diff --git a/.eslintignore b/.eslintignore index 2577b07cec12e8..b6206ec553a90f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,7 @@ .cache build build-module +build-types node_modules packages/block-serialization-spec-parser/parser.js packages/e2e-tests/plugins diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 68612ece606d5f..033ecb9d331c06 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,14 +4,14 @@ /docs/designers-developers/designers @chrisvanpatten @mkaz @ajitbohra # Data -/packages/api-fetch @youknowriad @nerrad @mmtr -/packages/core-data @youknowriad @nerrad -/packages/data @youknowriad @nerrad @coderkevin -/packages/redux-routine @youknowriad @nerrad +/packages/api-fetch @nerrad @mmtr +/packages/core-data @nerrad +/packages/data @nerrad @coderkevin +/packages/redux-routine @nerrad /packages/data-controls @nerrad # Blocks -/packages/block-library @Soean @ajitbohra @talldan +/packages/block-library @ajitbohra @talldan /packages/block-library/src/gallery @mkevins /packages/block-library/src/social-links @mkaz /packages/block-library/src/social-link @mkaz @@ -20,13 +20,13 @@ # Editor /packages/annotations @atimmer @ellatrix /packages/autop -/packages/block-editor @youknowriad @ellatrix +/packages/block-editor @ellatrix /packages/block-serialization-spec-parser @dmsnell /packages/block-serialization-default-parser @dmsnell -/packages/blocks @youknowriad @ellatrix +/packages/blocks @ellatrix /packages/edit-post /packages/editor -/packages/list-reusable-blocks @youknowriad +/packages/list-reusable-blocks /packages/shortcode /packages/block-directory /packages/interface @@ -34,18 +34,18 @@ /packages/server-side-render # Widgets -/packages/edit-widgets @youknowriad @draganescu @talldan @noisysocks @tellthemachines @adamziel @kevin940726 +/packages/edit-widgets @draganescu @talldan @noisysocks @tellthemachines @adamziel @kevin940726 # Navigation /packages/edit-navigation @draganescu @talldan @noisysocks @tellthemachines @adamziel @kevin940726 # Full Site Editing -/packages/edit-site @youknowriad +/packages/edit-site # Tooling /bin @ntwb @nerrad @ajitbohra /bin/api-docs @ntwb @nerrad @ajitbohra @nosolosw -/docs/tool @youknowriad @chrisvanpatten @ajitbohra @nosolosw +/docs/tool @chrisvanpatten @ajitbohra @nosolosw /packages/babel-plugin-import-jsx-pragma @gziolo @ntwb @nerrad @ajitbohra /packages/babel-plugin-makepot @ntwb @nerrad @ajitbohra /packages/babel-preset-default @gziolo @ntwb @nerrad @ajitbohra @@ -61,15 +61,15 @@ /packages/jest-puppeteer-axe @gziolo @ntwb @nerrad @ajitbohra /packages/library-export-default-webpack-plugin @gziolo @ntwb @nerrad @ajitbohra /packages/npm-package-json-lint-config @gziolo @ntwb @nerrad @ajitbohra -/packages/postcss-themes @youknowriad @ntwb @nerrad @ajitbohra +/packages/postcss-themes @ntwb @nerrad @ajitbohra /packages/scripts @gziolo @ntwb @nerrad @ajitbohra /packages/dependency-extraction-webpack-plugin @gziolo /packages/prettier-config @ntwb @gziolo # UI Components -/packages/components @youknowriad @ajitbohra @jaymanpandya @chrisvanpatten -/packages/compose @youknowriad @ajitbohra @jaymanpandya -/packages/element @youknowriad @ajitbohra @jaymanpandya +/packages/components @ajitbohra @jaymanpandya @chrisvanpatten +/packages/compose @ajitbohra @jaymanpandya +/packages/element @ajitbohra @jaymanpandya /packages/notices @ajitbohra @jaymanpandya /packages/nux @ajitbohra @jaymanpandya /packages/viewport @ajitbohra @jaymanpandya @@ -78,10 +78,10 @@ /packages/primitives # Utilities -/packages/a11y @youknowriad +/packages/a11y /packages/blob /packages/date -/packages/deprecated @youknowriad +/packages/deprecated /packages/dom @ellatrix /packages/dom-ready /packages/escape-html @@ -89,7 +89,7 @@ /packages/i18n @swissspidy /packages/is-shallow-equal /packages/keycodes @talldan @ellatrix -/packages/priority-queue @youknowriad +/packages/priority-queue /packages/token-list /packages/url @talldan /packages/wordcount @@ -101,19 +101,21 @@ /packages/plugins @gziolo @adamsilverstein # Rich Text -/packages/format-library @ellatrix @etoledom @cameronvoell @guarani -/packages/rich-text @ellatrix @etoledom @cameronvoell @guarani -/packages/block-editor/src/components/rich-text @ellatrix @etoledom @cameronvoell @guarani +/packages/format-library @ellatrix @cameronvoell @guarani +/packages/rich-text @ellatrix @cameronvoell @guarani +/packages/block-editor/src/components/rich-text @ellatrix @cameronvoell @guarani # Project Management -/.github @youknowriad @mapk @karmatosed +/.github @mapk @karmatosed /packages/project-management-automation # wp-env -/packages/env @epiqueras @noahtallen +/packages/env @noahtallen # PHP /lib @timothybjacobs @spacedmonkey +/lib/global-styles.php @timothybjabocs @spacedmonkey @nosolosw +/lib/experimental-default-theme.json @timothybjabocs @spacedmonkey @nosolosw # Native (Unowned) *.native.js @ghost diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md new file mode 100644 index 00000000000000..e813100a83a9a5 --- /dev/null +++ b/.github/SUPPORT.md @@ -0,0 +1,12 @@ + +# Gutenberg Project Support + +Welcome to Gutenberg, a WordPress project. We hope you join us in creating the future platform for publishing; all are welcome here. + +* Please see the [Contributing Guidelines](https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md) for additional information on how to contribute. + +* As with all WordPress projects, we want to ensure a welcoming environment for everyone. With that in mind, all contributors are expected to follow our [Code of Conduct](https://github.com/WordPress/gutenberg/blob/master/CODE_OF_CONDUCT.md). + +* Join us on Slack for real-time communication, it is where maintainers coordinate around the project. To get started using Slack, see: https://make.wordpress.org/chat/ + +* For general WordPress support with the core editor, see the [WordPress.org support forums](https://wordpress.org/support/) — it is highly active and well maintained. diff --git a/.github/workflows/build-plugin-zip.yml b/.github/workflows/build-plugin-zip.yml new file mode 100644 index 00000000000000..52218898bed0d8 --- /dev/null +++ b/.github/workflows/build-plugin-zip.yml @@ -0,0 +1,49 @@ +name: Build Gutenberg Plugin Zip + +on: + pull_request: + paths-ignore: + - '**.md' + push: + branches: [master] + tags: + - 'v*' + paths-ignore: + - '**.md' + +jobs: + build: + name: Build Release Artifact + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@master + + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + # npm cache files are stored in `~/.npm` on Linux/macOS + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: Use Node.js 12.x + uses: actions/setup-node@v1 + with: + node-version: 12.x + + - name: Build Gutenberg plugin ZIP file + run: ./bin/build-plugin-zip.sh + env: + NO_CHECKS: 'true' + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: gutenberg-plugin + path: ./gutenberg.zip \ No newline at end of file diff --git a/.github/workflows/bundle-size.yml b/.github/workflows/bundle-size.yml index caba6ffb0bf03c..e1e6c292fd24df 100644 --- a/.github/workflows/bundle-size.yml +++ b/.github/workflows/bundle-size.yml @@ -12,7 +12,7 @@ jobs: with: fetch-depth: 1 - - uses: preactjs/compressed-size-action@v1 + - uses: preactjs/compressed-size-action@v2 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" pattern: "{build/**/*.js,build/**/*.css}" diff --git a/.github/workflows/cancel.yml b/.github/workflows/cancel.yml index b51369c13098a0..e65d084e10d840 100644 --- a/.github/workflows/cancel.yml +++ b/.github/workflows/cancel.yml @@ -7,7 +7,7 @@ jobs: timeout-minutes: 3 steps: - name: Get all workflow ids and set to env variable - run: echo ::set-env name=WORKFLOW_IDS_TO_CANCEL::$(curl https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/workflows -s | jq -r '.workflows | map(.id|tostring) | join(",")') + run: echo "WORKFLOW_IDS_TO_CANCEL=$(curl https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/workflows -s | jq -r '.workflows | map(.id|tostring) | join(",")')" >> $GITHUB_ENV - uses: styfle/cancel-workflow-action@0.4.0 with: diff --git a/.github/workflows/create-block.yml b/.github/workflows/create-block.yml index f585ecefc4c131..65a39db4936932 100644 --- a/.github/workflows/create-block.yml +++ b/.github/workflows/create-block.yml @@ -34,10 +34,10 @@ jobs: ${{ runner.os }}-build- ${{ runner.os }}- - - name: Use Node.js 12.x + - name: Use Node.js 14.x uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 14.x - name: npm install, build, format and lint run: | diff --git a/.github/workflows/end2end-test.yml b/.github/workflows/end2end-test.yml index 37da6d7fcdd7bc..a42371f5e13692 100644 --- a/.github/workflows/end2end-test.yml +++ b/.github/workflows/end2end-test.yml @@ -10,92 +10,16 @@ on: - '**.md' jobs: - admin-1: - name: Admin - 1 + admin: + name: Admin - ${{ matrix.part }} runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Cache node modules - uses: actions/cache@v2 - env: - cache-name: cache-node-modules - with: - # npm cache files are stored in `~/.npm` on Linux/macOS - path: ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - - name: Use Node.js 12.x - uses: actions/setup-node@v1 - with: - node-version: 12.x - - - name: Npm install and build - run: | - npm ci - FORCE_REDUCED_MOTION=true npm run build - - - name: Install WordPress - run: | - chmod -R 767 ./ # TODO: Possibly integrate in wp-env - npm run wp-env start - - - name: Running the tests - run: | - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 0' < ~/.jest-e2e-tests ) - - admin-2: - name: Admin - 2 - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Cache node modules - uses: actions/cache@v2 - env: - cache-name: cache-node-modules - with: - # npm cache files are stored in `~/.npm` on Linux/macOS - path: ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - - name: Use Node.js 12.x - uses: actions/setup-node@v1 - with: - node-version: 12.x - - - name: Npm install and build - run: | - npm ci - FORCE_REDUCED_MOTION=true npm run build - - - name: Install WordPress - run: | - chmod -R 767 ./ # TODO: Possibly integrate in wp-env - npm run wp-env start - - - name: Running the tests - run: | - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 1' < ~/.jest-e2e-tests ) + strategy: + fail-fast: false + matrix: + part: [1, 2, 3, 4] - admin-3: - name: Admin - 3 - - runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -113,10 +37,10 @@ jobs: ${{ runner.os }}-build- ${{ runner.os }}- - - name: Use Node.js 12.x + - name: Use Node.js 14.x uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 14.x - name: Npm install and build run: | @@ -131,45 +55,11 @@ jobs: - name: Running the tests run: | $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 2' < ~/.jest-e2e-tests ) - - admin-4: - name: Admin - 4 - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Cache node modules - uses: actions/cache@v2 - env: - cache-name: cache-node-modules - with: - # npm cache files are stored in `~/.npm` on Linux/macOS - path: ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- + $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == ${{ matrix.part }} - 1' < ~/.jest-e2e-tests ) - - name: Use Node.js 12.x - uses: actions/setup-node@v1 + - name: Archive debug artifacts (screenshots, HTML snapshots) + uses: actions/upload-artifact@v2 + if: always() with: - node-version: 12.x - - - name: Npm install and build - run: | - npm ci - FORCE_REDUCED_MOTION=true npm run build - - - name: Install WordPress - run: | - chmod -R 767 ./ # TODO: Possibly integrate in wp-env - npm run wp-env start - - - name: Running the tests - run: | - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 3' < ~/.jest-e2e-tests ) + name: failures-artifacts + path: artifacts diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 8d1bfadc5d7b3c..1c6be548c7a519 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -27,10 +27,10 @@ jobs: ${{ runner.os }}-build- ${{ runner.os }}- - - name: Use Node.js 12.x + - name: Use Node.js 14.x uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 14.x - name: Npm install run: | diff --git a/.github/workflows/pull-request-automation.yml b/.github/workflows/pull-request-automation.yml index a19fb0f1a761f4..96117582cc408e 100644 --- a/.github/workflows/pull-request-automation.yml +++ b/.github/workflows/pull-request-automation.yml @@ -1,5 +1,5 @@ on: - pull_request: + pull_request_target: types: [opened] push: name: Pull request automation diff --git a/.github/workflows/rnmobile-android-runner.yml b/.github/workflows/rnmobile-android-runner.yml index 0bb8a0c4455ccf..b88529ebb16eaa 100644 --- a/.github/workflows/rnmobile-android-runner.yml +++ b/.github/workflows/rnmobile-android-runner.yml @@ -27,6 +27,8 @@ jobs: with: path: ~/.npm key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }} + restore-keys: | + ${{ runner.os }}-npm- - run: npm ci diff --git a/.github/workflows/rnmobile-ios-runner.yml b/.github/workflows/rnmobile-ios-runner.yml index 88d0469eeed09a..3e8e383065839f 100644 --- a/.github/workflows/rnmobile-ios-runner.yml +++ b/.github/workflows/rnmobile-ios-runner.yml @@ -26,6 +26,8 @@ jobs: with: path: ~/.npm key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }} + restore-keys: | + ${{ runner.os }}-npm- - run: npm ci @@ -57,10 +59,10 @@ jobs: run: npm run native test:e2e:bundle:ios - name: Switch Xcode Version - run: sudo xcode-select --switch /Applications/Xcode_11.4.1.app + run: sudo xcode-select --switch /Applications/Xcode_12.app - name: Build (if needed) - run: test -e packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app/gutenberg || SKIP_BUNDLING=true npm run native test:e2e:build-app:ios + run: test -e packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app/gutenberg || npm run native test:e2e:build-app:ios - name: Run iOS Device Tests run: TEST_RN_PLATFORM=ios npm run native device-tests:local ${{ matrix.native-test-name }} diff --git a/.github/workflows/static-checks.yml b/.github/workflows/static-checks.yml index fd447c42542095..9ca88f6d094c91 100644 --- a/.github/workflows/static-checks.yml +++ b/.github/workflows/static-checks.yml @@ -27,10 +27,10 @@ jobs: ${{ runner.os }}-build- ${{ runner.os }}- - - name: Use Node.js 12.x + - name: Use Node.js 14.x uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 14.x - name: Npm install and build # A "full" install is executed, since `npm ci` does not always exit diff --git a/.github/workflows/storybook-pages.yml b/.github/workflows/storybook-pages.yml index 3e168c8e806fc6..0c66e9b9e92e55 100644 --- a/.github/workflows/storybook-pages.yml +++ b/.github/workflows/storybook-pages.yml @@ -30,7 +30,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v1 with: - node-version: '12.x' + node-version: '14.x' - name: Install Dependencies run: npm ci diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index e229ab384abc2c..35aa4c4a1a34b8 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -31,10 +31,10 @@ jobs: ${{ runner.os }}-build- ${{ runner.os }}- - - name: Use Node.js 12.x + - name: Use Node.js 14.x uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 14.x - name: Npm install and build # It's not necessary to run the full build, since Jest can interpret @@ -47,6 +47,9 @@ jobs: - name: Running the tests run: npm run test-unit -- --ci --maxWorkers=2 --cacheDirectory="$HOME/.jest-cache" + - name: Running the date tests + run: npm run test-unit:date -- --ci --maxWorkers=2 --cacheDirectory="$HOME/.jest-cache" + unit-php: name: PHP @@ -68,10 +71,10 @@ jobs: ${{ runner.os }}-build- ${{ runner.os }}- - - name: Use Node.js 12.x + - name: Use Node.js 14.x uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 14.x - name: Npm install and build run: | @@ -83,8 +86,17 @@ jobs: chmod -R 767 ./ # TODO: Possibly integrate in wp-env npm run wp-env start - - name: Running the tests - run: npm run test-php && npm run test-unit-php-multisite + - name: Running lint check + run: npm run lint-php + + - name: Running single site unit tests + run: npm run test-unit-php + if: ${{ success() || failure() }} + + - name: Running multisite unit tests + run: npm run test-unit-php-multisite + if: ${{ success() || failure() }} + mobile-unit-js: name: Mobile @@ -107,10 +119,10 @@ jobs: ${{ runner.os }}-build- ${{ runner.os }}- - - name: Use Node.js 12.x + - name: Use Node.js 14.x uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 14.x - name: Npm install and build # It's not necessary to run the full build, since Jest can interpret diff --git a/.gitignore b/.gitignore index a3ebac937fc66c..cf7a035b43b060 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ gutenberg.zip phpcs.xml yarn.lock /wordpress +/artifacts playground/dist .cache @@ -22,3 +23,5 @@ test/native/junit.xml # Local overrides .wp-env.override.json +phpunit.xml +phpunit-watcher.yml diff --git a/.nvmrc b/.nvmrc index 48082f72f087ce..b009dfb9d9f98f 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -12 +lts/* diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000000000..3e333f450a0013 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Listen for Xdebug", + "type": "php", + "request": "launch", + "port": 9003, + "pathMappings": { + "/var/www/html/wp-content/plugins/gutenberg": "${workspaceRoot}/" + } + } + ] +} \ No newline at end of file diff --git a/.wp-env.json b/.wp-env.json index f1a87f013c1d11..41ecee8042bb17 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -3,6 +3,7 @@ "plugins": [ "." ], + "themes": [ "WordPress/theme-experiments/twentytwentyone-blocks" ], "env": { "tests": { "mappings": { diff --git a/README.md b/README.md index aab63190c132f0..b5570c6dafdd13 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lerna.js.org) -![Screenshot of the Gutenberg Editor, editing a post in WordPress](https://user-images.githubusercontent.com/1204802/73433964-2540d900-4346-11ea-94f3-5df2e9d876bc.png) +![Screenshot of the Gutenberg Editor, editing a post in WordPress](https://user-images.githubusercontent.com/1204802/100067796-fc3e8700-2e36-11eb-993b-6b80b4310b87.png) Welcome to the development hub for the WordPress Gutenberg project! @@ -39,7 +39,7 @@ Review the [Create a Block tutorial](/docs/designers-developers/developers/tutor ### Contribute to Gutenberg -Gutenberg is an open-source project and welcomes all contributors from code to design, from documentation to triage. The project is built by many [contributors and volunteers](https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTORS.md) and we'd love your help building it. +Gutenberg is an open-source project and welcomes all contributors from code to design, from documentation to triage. The project is built by many contributors and volunteers and we'd love your help building it. See the [Contributors Handbook](https://developer.wordpress.org/block-editor/contributors/) for all the details on how you can contribute. See [CONTRIBUTING.md](https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md) for the contributing guidelines. diff --git a/bin/build-plugin-zip.sh b/bin/build-plugin-zip.sh index c44e5e3db50811..a8e91093205a2f 100755 --- a/bin/build-plugin-zip.sh +++ b/bin/build-plugin-zip.sh @@ -99,7 +99,7 @@ done # Run the build. status "Installing dependencies... 📦" -npm install +npm ci status "Generating build... 👷‍♀️" npm run build diff --git a/bin/generate-gutenberg-php.php b/bin/generate-gutenberg-php.php index fc9bf51ef3cab5..4ed6b661a21c23 100755 --- a/bin/generate-gutenberg-php.php +++ b/bin/generate-gutenberg-php.php @@ -7,7 +7,7 @@ * @package gutenberg-build */ -$f = fopen( dirname( dirname( __FILE__ ) ) . '/gutenberg.php', 'r' ); +$f = fopen( dirname( __DIR__ ) . '/gutenberg.php', 'r' ); $plugin_version = null; $inside_defines = false; diff --git a/bin/get-vendor-scripts.php b/bin/get-vendor-scripts.php index e7295f556b4cb2..9db830d564e3cf 100755 --- a/bin/get-vendor-scripts.php +++ b/bin/get-vendor-scripts.php @@ -10,7 +10,7 @@ define( 'SCRIPT_DEBUG', $argc > 1 && 'debug' === strtolower( $argv[1] ) ); // Hacks to get lib/client-assets.php to load. -define( 'ABSPATH', dirname( dirname( __FILE__ ) ) ); +define( 'ABSPATH', dirname( __DIR__ ) ); /** * Hi, phpcs @@ -30,7 +30,7 @@ function wp_add_inline_script() {} // Instead of loading script files, just show how they need to be loaded. define( 'GUTENBERG_LIST_VENDOR_ASSETS', true ); -require_once dirname( dirname( __FILE__ ) ) . '/lib/client-assets.php'; +require_once dirname( __DIR__ ) . '/lib/client-assets.php'; /** * Hi, phpcs diff --git a/bin/packages/build-worker.js b/bin/packages/build-worker.js index e0de0fd401181c..31e5c3bb17ed8b 100644 --- a/bin/packages/build-worker.js +++ b/bin/packages/build-worker.js @@ -6,7 +6,7 @@ const fs = require( 'fs' ); const path = require( 'path' ); const babel = require( '@babel/core' ); const makeDir = require( 'make-dir' ); -const sass = require( 'node-sass' ); +const sass = require( 'sass' ); const postcss = require( 'postcss' ); /** * Internal dependencies diff --git a/bin/packages/build.js b/bin/packages/build.js index fa837de240ea8b..f2a9e381875f2e 100755 --- a/bin/packages/build.js +++ b/bin/packages/build.js @@ -1,6 +1,7 @@ /** * External dependencies */ +const fs = require( 'fs' ); const path = require( 'path' ); const glob = require( 'fast-glob' ); const ProgressBar = require( 'progress' ); @@ -16,6 +17,10 @@ const files = process.argv.slice( 2 ); */ const PACKAGES_DIR = path.resolve( __dirname, '../../packages' ); +const stylesheetEntryPoints = glob.sync( + path.resolve( PACKAGES_DIR, '*/src/*.scss' ) +); + /** * Get the package name for a specified file * @@ -26,6 +31,57 @@ function getPackageName( file ) { return path.relative( PACKAGES_DIR, file ).split( path.sep )[ 0 ]; } +/** + * Parses all Sass import statements in a given file + * + * @param {string} file File name + * @return {Array} List of Import Statements in a file + */ +function parseImportStatements( file ) { + const fileContent = fs.readFileSync( file, 'utf8' ); + return fileContent.toString().match( /@import "(.*?)"/g ); +} + +function isFileImportedInStyleEntry( file, importStatements ) { + const packageName = getPackageName( file ); + const regex = new RegExp( `/${ packageName }/`, 'g' ); + + return ( + importStatements && + importStatements.find( ( importStatement ) => + importStatement.match( regex ) + ) + ); +} + +/** + * Finds all stylesheet entry points that contain import statements + * that include the given file name + * + * @param {string} file File name + * @return {Array} List of entry points that import the styles from the file + */ +function findStyleEntriesThatImportFile( file ) { + const entriesWithImport = stylesheetEntryPoints.reduce( + ( acc, entryPoint ) => { + const styleEntryImportStatements = parseImportStatements( + entryPoint + ); + + if ( + isFileImportedInStyleEntry( file, styleEntryImportStatements ) + ) { + acc.push( entryPoint ); + } + + return acc; + }, + [] + ); + + return entriesWithImport; +} + /** * Returns a stream transform which maps an individual stylesheet to its * package entrypoint. Unlike JavaScript which uses an external bundler to @@ -59,6 +115,16 @@ function createStyleEntryTransform() { path.resolve( PACKAGES_DIR, packageName, 'src/*.scss' ) ); entries.forEach( ( entry ) => this.push( entry ) ); + + // Find other stylesheets that need to be rebuilt because + // they import the styles that are being transformed + const styleEntries = findStyleEntriesThatImportFile( file ); + + // Rebuild stylesheets that import the styles being transformed + if ( styleEntries.length ) { + styleEntries.forEach( ( entry ) => stream.push( entry ) ); + } + callback(); }, } ); @@ -109,7 +175,10 @@ let stream; if ( files.length ) { stream = new Readable( { encoding: 'utf8' } ); - files.forEach( ( file ) => stream.push( file ) ); + files.forEach( ( file ) => { + stream.push( file ); + } ); + stream.push( null ); stream = stream .pipe( createStyleEntryTransform() ) @@ -124,7 +193,14 @@ if ( files.length ) { bar.tick( 0 ); stream = glob.stream( - [ `${ PACKAGES_DIR }/*/src/**/*.js`, `${ PACKAGES_DIR }/*/src/*.scss` ], + [ + `${ PACKAGES_DIR }/*/src/**/*.js`, + `${ PACKAGES_DIR }/*/src/*.scss`, + `${ PACKAGES_DIR }/block-library/src/**/*.js`, + `${ PACKAGES_DIR }/block-library/src/*/style.scss`, + `${ PACKAGES_DIR }/block-library/src/*/editor.scss`, + `${ PACKAGES_DIR }/block-library/src/*.scss`, + ], { ignore: [ `**/benchmark/**`, diff --git a/bin/plugin/cli.js b/bin/plugin/cli.js index 054199ef4b4b10..84db72639f6b2a 100755 --- a/bin/plugin/cli.js +++ b/bin/plugin/cli.js @@ -30,9 +30,7 @@ const { runPerformanceTests } = require( './commands/performance' ); program .command( 'release-plugin-rc' ) .alias( 'rc' ) - .description( - 'Release an RC version of the plugin (supports only rc.1 for now)' - ) + .description( 'Release an RC version of the plugin' ) .action( catchException( releaseRC ) ); program diff --git a/bin/plugin/commands/common.js b/bin/plugin/commands/common.js index d32b687720605b..5a300e50e68c2c 100644 --- a/bin/plugin/commands/common.js +++ b/bin/plugin/commands/common.js @@ -74,8 +74,71 @@ function findReleaseBranchName( packageJsonPath ) { ); } +/** + * Calculates version bump for the packages based on the content + * from the provided CHANGELOG file split into individual lines. + * + * @param {string[]} lines Changelog content split into lines. + * @param {('patch'|'minor'|'major')} [minimumVersionBump] Minimum version bump for the package. + * Defaults to `patch`. + * + * @return {string|null} Version bump when applicable, or null otherwise. + */ +function calculateVersionBumpFromChangelog( + lines, + minimumVersionBump = 'patch' +) { + let changesDetected = false; + let versionBump = null; + for ( const line of lines ) { + const lineNormalized = line.toLowerCase().trimLeft(); + // Detect unpublished changes first. + if ( lineNormalized.startsWith( '## unreleased' ) ) { + changesDetected = true; + continue; + } + + // Skip all lines until unpublished changes found. + if ( ! changesDetected ) { + continue; + } + + // A previous published version detected. Stop processing. + if ( lineNormalized.startsWith( '## ' ) ) { + break; + } + + // A major version bump required. Stop processing. + if ( lineNormalized.startsWith( '### breaking change' ) ) { + versionBump = 'major'; + break; + } + + // A minor version bump required. Proceed to the next line. + if ( + lineNormalized.startsWith( '### deprecation' ) || + lineNormalized.startsWith( '### enhancement' ) || + lineNormalized.startsWith( '### new feature' ) + ) { + versionBump = 'minor'; + continue; + } + + // A version bump required. Found new changelog section. + if ( + versionBump !== 'minor' && + ( lineNormalized.startsWith( '### ' ) || + lineNormalized.includes( '- ' ) ) + ) { + versionBump = minimumVersionBump; + } + } + return versionBump; +} + module.exports = { + calculateVersionBumpFromChangelog, + findReleaseBranchName, runGitRepositoryCloneStep, runCleanLocalFoldersStep, - findReleaseBranchName, }; diff --git a/bin/plugin/commands/packages.js b/bin/plugin/commands/packages.js index 9a16beeb82b06f..6291008ae575af 100644 --- a/bin/plugin/commands/packages.js +++ b/bin/plugin/commands/packages.js @@ -6,6 +6,7 @@ const glob = require( 'fast-glob' ); const fs = require( 'fs' ); const semver = require( 'semver' ); const readline = require( 'readline' ); +const { prompt } = require( 'inquirer' ); /** * Internal dependencies @@ -13,12 +14,19 @@ const readline = require( 'readline' ); const { log, formats } = require( '../lib/logger' ); const { askForConfirmation, runStep, readJSONFile } = require( '../lib/utils' ); const { + calculateVersionBumpFromChangelog, + findReleaseBranchName, runGitRepositoryCloneStep, runCleanLocalFoldersStep, - findReleaseBranchName, } = require( './common' ); const git = require( '../lib/git' ); +/** + * Semantic Versioning labels. + * + * @typedef {('major'|'minor'|'patch')} SemVer + */ + /** * Checks out the WordPress release branch and syncs it with the changes from * the last plugin release. @@ -87,7 +95,7 @@ async function runWordPressReleaseBranchSyncStep( * contain new entries. * * @param {string} gitWorkingDirectoryPath Git working directory path. - * @param {string} minimumVersionBump Minimum version bump for the packages. + * @param {SemVer} minimumVersionBump Minimum version bump for the packages. * @param {boolean} isPrerelease Whether the package version to publish is a prerelease. * @param {string} abortMessage Abort Message. */ @@ -104,55 +112,18 @@ async function updatePackages( changelogFiles.map( async ( changelogPath ) => { const fileStream = fs.createReadStream( changelogPath ); - const lines = readline.createInterface( { + const rl = readline.createInterface( { input: fileStream, } ); - - let changesDetected = false; - let versionBump = null; - for await ( const line of lines ) { - const lineNormalized = line.toLowerCase(); - // Detect unpublished changes first. - if ( lineNormalized.startsWith( '## unreleased' ) ) { - changesDetected = true; - continue; - } - - // Skip all lines until unpublished changes found. - if ( ! changesDetected ) { - continue; - } - - // A previous published version detected. Stop processing. - if ( lineNormalized.startsWith( '## ' ) ) { - break; - } - - // A major version bump required. Stop processing. - if ( lineNormalized.startsWith( '### breaking change' ) ) { - versionBump = 'major'; - break; - } - - // A minor version bump required. Proceed to the next line. - if ( - lineNormalized.startsWith( '### deprecation' ) || - lineNormalized.startsWith( '### enhancement' ) || - lineNormalized.startsWith( '### new feature' ) - ) { - versionBump = 'minor'; - continue; - } - - // A version bump required. Found new changelog section. - if ( - versionBump !== 'minor' && - ( lineNormalized.startsWith( '### ' ) || - lineNormalized.startsWith( '- initial release' ) ) - ) { - versionBump = minimumVersionBump; - } + const lines = []; + for await ( const line of rl ) { + lines.push( line ); } + + const versionBump = calculateVersionBumpFromChangelog( + lines, + minimumVersionBump + ); const packageName = `@wordpress/${ changelogPath.split( '/' ).reverse()[ 1 ] }`; @@ -274,12 +245,11 @@ async function runPushGitChangesStep( /** * Prepare everything to publish WordPress packages to npm. * - * @param {string} minimumVersionBump Minimum version bump for the packages. - * @param {boolean} isPrerelease Whether the package version to publish is a prerelease. + * @param {boolean} [isPrerelease] Whether the package version to publish is a prerelease. * * @return {Promise} Github release object. */ -async function prepareForPackageRelease( minimumVersionBump, isPrerelease ) { +async function prepareForPackageRelease( isPrerelease ) { // This is a variable that contains the abort message shown when the script is aborted. let abortMessage = 'Aborting!'; const temporaryFolders = []; @@ -297,6 +267,16 @@ async function prepareForPackageRelease( minimumVersionBump, isPrerelease ) { abortMessage ); + const { minimumVersionBump } = await prompt( [ + { + type: 'list', + name: 'minimumVersionBump', + message: 'Select the minimum version bump for packages:', + default: 'patch', + choices: [ 'patch', 'minor', 'major' ], + }, + ] ); + await updatePackages( gitWorkingDirectoryPath, minimumVersionBump, @@ -316,6 +296,9 @@ async function prepareForPackageRelease( minimumVersionBump, isPrerelease ) { await runCleanLocalFoldersStep( temporaryFolders, abortMessage ); } +/** + * Prepares everything for publishing a new stable version of WordPress packages. + */ async function prepareLatestDistTag() { log( formats.title( @@ -325,7 +308,7 @@ async function prepareLatestDistTag() { "To perform a release you'll have to be a member of the WordPress Team on npm.\n" ); - await prepareForPackageRelease( 'patch' ); + await prepareForPackageRelease(); log( '\n>> 🎉 WordPress packages are ready to publish!\n', @@ -334,6 +317,9 @@ async function prepareLatestDistTag() { ); } +/** + * Prepares everything for publishing a new RC version of WordPress packages. + */ async function prepareNextDistTag() { log( formats.title( @@ -343,7 +329,7 @@ async function prepareNextDistTag() { "To perform a release you'll have to be a member of the WordPress Team on npm.\n" ); - await prepareForPackageRelease( 'minor', true ); + await prepareForPackageRelease( true ); log( '\n>> 🎉 WordPress packages are ready to publish!\n', diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index f271e66268a6fb..22f7f2e7eade47 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -30,29 +30,43 @@ const config = require( '../config' ); * @property {number[]} load Load Time. * @property {number[]} type Average type time. * @property {number[]} focus Average block selection time. + * @property {number[]} inserterOpen Average time to open global inserter. + * @property {number[]} inserterHover Average time to move mouse between two block item in the inserter. */ /** * @typedef WPPerformanceResults * - * @property {number} load Load Time. - * @property {number} type Average type time. - * @property {number} minType Minium type time. - * @property {number} maxType Maximum type time. - * @property {number} focus Average block selection time. - * @property {number} minFocus Min block selection time. - * @property {number} maxFocus Max block selection time. + * @property {number} load Load Time. + * @property {number} type Average type time. + * @property {number} minType Minium type time. + * @property {number} maxType Maximum type time. + * @property {number} focus Average block selection time. + * @property {number} minFocus Min block selection time. + * @property {number} maxFocus Max block selection time. + * @property {number} inserterOpen Average time to open global inserter. + * @property {number} minInserterOpen Min time to open global inserter. + * @property {number} maxInserterOpen Max time to open global inserter. + * @property {number} inserterHover Average time to move mouse between two block item in the inserter. + * @property {number} minInserterHover Min time to move mouse between two block item in the inserter. + * @property {number} maxInserterHover Max time to move mouse between two block item in the inserter. */ /** * @typedef WPFormattedPerformanceResults * - * @property {string=} load Load Time. - * @property {string=} type Average type time. - * @property {string=} minType Minium type time. - * @property {string=} maxType Maximum type time. - * @property {string=} focus Average block selection time. - * @property {string=} minFocus Min block selection time. - * @property {string=} maxFocus Max block selection time. + * @property {string=} load Load Time. + * @property {string=} type Average type time. + * @property {string=} minType Minium type time. + * @property {string=} maxType Maximum type time. + * @property {string=} focus Average block selection time. + * @property {string=} minFocus Min block selection time. + * @property {string=} maxFocus Max block selection time. + * @property {string=} inserterOpen Average time to open global inserter. + * @property {string=} minInserterOpen Min time to open global inserter. + * @property {string=} maxInserterOpen Max time to open global inserter. + * @property {string=} inserterHover Average time to move mouse between two block item in the inserter. + * @property {string=} minInserterHover Min time to move mouse between two block item in the inserter. + * @property {string=} maxInserterHover Max time to move mouse between two block item in the inserter. */ /** @@ -109,6 +123,12 @@ function curateResults( results ) { focus: average( results.focus ), minFocus: Math.min( ...results.focus ), maxFocus: Math.max( ...results.focus ), + inserterOpen: average( results.inserterOpen ), + minInserterOpen: Math.min( ...results.inserterOpen ), + maxInserterOpen: Math.max( ...results.inserterOpen ), + inserterHover: average( results.inserterHover ), + minInserterHover: Math.min( ...results.inserterHover ), + maxInserterHover: Math.max( ...results.inserterHover ), }; } @@ -166,6 +186,12 @@ async function runTestSuite( testSuite, performanceTestDirectory ) { focus: results.map( ( r ) => r.focus ), minFocus: results.map( ( r ) => r.minFocus ), maxFocus: results.map( ( r ) => r.maxFocus ), + inserterOpen: results.map( ( r ) => r.inserterOpen ), + minInserterOpen: results.map( ( r ) => r.minInserterOpen ), + maxInserterOpen: results.map( ( r ) => r.maxInserterOpen ), + inserterHover: results.map( ( r ) => r.inserterHover ), + minInserterHover: results.map( ( r ) => r.minInserterHover ), + maxInserterHover: results.map( ( r ) => r.maxInserterHover ), }, median ); @@ -268,7 +294,21 @@ async function runPerformanceTests( branches, options ) { log( '\n>> 🎉 Results.\n' ); for ( const testSuite of testSuites ) { log( `\n>> ${ testSuite }\n` ); - console.table( results[ testSuite ] ); + + /** @type {Record>} */ + const invertedResult = {}; + Object.entries( results[ testSuite ] ).reduce( + ( acc, [ key, val ] ) => { + for ( const entry of Object.keys( val ) ) { + if ( ! acc[ entry ] ) acc[ entry ] = {}; + // @ts-ignore + acc[ entry ][ key ] = val[ entry ]; + } + return acc; + }, + invertedResult + ); + console.table( invertedResult ); } } diff --git a/bin/plugin/commands/release.js b/bin/plugin/commands/release.js index 9df33d8987b079..56a232d13882c4 100644 --- a/bin/plugin/commands/release.js +++ b/bin/plugin/commands/release.js @@ -58,32 +58,20 @@ async function runSvnRepositoryCheckoutStep( abortMessage ) { /** * Creates a new release branch based on the last package.json version - * and chooses the next RC version number. + * and chooses the next version number. * * @param {string} gitWorkingDirectoryPath Git Working Directory Path. * @param {string} abortMessage Abort Message. - * - * @return {Promise} chosen version and versionLabels. + * @param {string} version the new version. + * @param {string} releaseBranch The release branch to push to. */ async function runReleaseBranchCreationStep( gitWorkingDirectoryPath, - abortMessage + abortMessage, + version, + releaseBranch ) { - let version, releaseBranch, versionLabel; await runStep( 'Creating the release branch', abortMessage, async () => { - const packageJsonPath = gitWorkingDirectoryPath + '/package.json'; - const packageJson = readJSONFile( packageJsonPath ); - const nextMajor = getNextMajorVersion( packageJson.version ); - const parsedMajorVersion = semver.parse( nextMajor ); - - version = nextMajor + '-rc.1'; - releaseBranch = - 'release/' + - parsedMajorVersion.major + - '.' + - parsedMajorVersion.minor; - versionLabel = nextMajor + ' RC1'; - await askForConfirmation( 'The Plugin version to be used is ' + formats.success( version ) + @@ -92,75 +80,46 @@ async function runReleaseBranchCreationStep( abortMessage ); - // Creating the release branch + // Creates the release branch await git.createLocalBranch( gitWorkingDirectoryPath, releaseBranch ); + log( '>> The local release branch ' + formats.success( releaseBranch ) + ' has been successfully created.' ); } ); - - return { - version, - versionLabel, - releaseBranch, - }; } /** - * Checkouts out the release branch and chooses a stable version number. + * Checkouts out the release branch. * * @param {string} gitWorkingDirectoryPath Git Working Directory Path. * @param {string} abortMessage Abort Message. - * - * @return {Promise} chosen version and versionLabels. + * @param {string} version The new version. + * @param {string} releaseBranch The release branch to checkout. */ async function runReleaseBranchCheckoutStep( gitWorkingDirectoryPath, - abortMessage + abortMessage, + version, + releaseBranch ) { - let releaseBranch, version; await runStep( 'Getting into the release branch', abortMessage, async () => { - const packageJsonPath = gitWorkingDirectoryPath + '/package.json'; - releaseBranch = findReleaseBranchName( packageJsonPath ); await git.checkoutRemoteBranch( gitWorkingDirectoryPath, releaseBranch ); + log( '>> The local release branch ' + formats.success( releaseBranch ) + ' has been successfully checked out.' ); - const releaseBranchPackageJson = readJSONFile( packageJsonPath ); - const releaseBranchParsedVersion = semver.parse( - releaseBranchPackageJson.version - ); - - if ( - releaseBranchParsedVersion.prerelease && - releaseBranchParsedVersion.prerelease.length - ) { - version = - releaseBranchParsedVersion.major + - '.' + - releaseBranchParsedVersion.minor + - '.' + - releaseBranchParsedVersion.patch; - } else { - version = - releaseBranchParsedVersion.major + - '.' + - releaseBranchParsedVersion.minor + - '.' + - ( releaseBranchParsedVersion.patch + 1 ); - } - await askForConfirmation( 'The Version to release is ' + formats.success( version ) + @@ -170,12 +129,6 @@ async function runReleaseBranchCheckoutStep( ); } ); - - return { - version, - versionLabel: version, - releaseBranch, - }; } /** @@ -371,10 +324,12 @@ async function runPushGitChangesStep( true, abortMessage ); + await git.pushBranchToOrigin( gitWorkingDirectoryPath, releaseBranch ); + await git.pushTagsToOrigin( gitWorkingDirectoryPath ); } ); @@ -437,52 +392,60 @@ async function runGithubReleaseStep( abortMessage ) { let octokit; + let releaseDraft; let release; - await runStep( 'Creating the GitHub release', abortMessage, async () => { - await askForConfirmation( - 'Proceed with the creation of the GitHub release?', - true, - abortMessage - ); - - const { token } = await inquirer.prompt( [ - { - type: 'input', - name: 'token', - message: - 'Please enter a GitHub personal authentication token.\n' + - 'You can create one by navigating to ' + - formats.success( - 'https://github.com/settings/tokens/new?scopes=repo,admin:org,write:packages' - ) + - '.\nToken:', - }, - ] ); - - octokit = new Octokit( { - auth: token, - } ); - const releaseData = await octokit.repos.createRelease( { - owner: config.githubRepositoryOwner, - repo: config.githubRepositoryName, - tag_name: 'v' + version, - name: versionLabel, - body: changelog, - prerelease: isPrerelease, - } ); - release = releaseData.data; + await runStep( + 'Creating the GitHub release draft', + abortMessage, + async () => { + await askForConfirmation( + 'Proceed with the creation of the GitHub release draft?', + true, + abortMessage + ); - log( '>> The GitHub release has been created.' ); - } ); + const { token } = await inquirer.prompt( [ + { + type: 'input', + name: 'token', + message: + 'Please enter a GitHub personal authentication token.\n' + + 'You can create one by navigating to ' + + formats.success( + 'https://github.com/settings/tokens/new?scopes=repo,admin:org,write:packages' + ) + + '.\nToken:', + }, + ] ); + + octokit = new Octokit( { + auth: token, + } ); + + const releaseDraftData = await octokit.repos.createRelease( { + owner: config.githubRepositoryOwner, + repo: config.githubRepositoryName, + tag_name: 'v' + version, + name: versionLabel, + body: changelog, + prerelease: isPrerelease, + draft: true, + } ); + releaseDraft = releaseDraftData.data; + + log( '>> The GitHub release draft has been created.' ); + } + ); abortMessage = - abortMessage + ' Make sure to remove the the GitHub release as well.'; + abortMessage + + ' Make sure to remove the the GitHub release draft as well.'; // Uploading the Zip to the Github release await runStep( 'Uploading the plugin ZIP', abortMessage, async () => { const filestats = fs.statSync( zipPath ); await octokit.repos.uploadReleaseAsset( { - url: release.upload_url, + url: releaseDraft.upload_url, headers: { 'content-length': filestats.size, 'content-type': 'application/zip', @@ -493,6 +456,19 @@ async function runGithubReleaseStep( log( '>> The plugin ZIP has been successfully uploaded.' ); } ); + // Remove draft status from the Github release + await runStep( 'Publishing the Github release', abortMessage, async () => { + const releaseData = await octokit.repos.updateRelease( { + owner: config.githubRepositoryOwner, + repo: config.githubRepositoryName, + release_id: releaseDraft.id, + draft: false, + } ); + release = releaseData.data; + + log( '>> The GitHub release has been published.' ); + } ); + log( '>> The GitHub release is available here: ' + formats.success( release.html_url ) @@ -684,6 +660,8 @@ async function isMilestoneClear( version ) { async function releasePlugin( isRC = true ) { // This is a variable that contains the abort message shown when the script is aborted. let abortMessage = 'Aborting!'; + let version, releaseBranch; + const temporaryFolders = []; await askForConfirmation( 'Ready to go? ' ); @@ -700,16 +678,57 @@ async function releasePlugin( isRC = true ) { const gitWorkingDirectory = await runGitRepositoryCloneStep( abortMessage ); temporaryFolders.push( gitWorkingDirectory ); - // Creating the release branch - const { version, versionLabel, releaseBranch } = isRC - ? await runReleaseBranchCreationStep( + const packageJsonPath = gitWorkingDirectory + '/package.json'; + const packageJson = readJSONFile( packageJsonPath ); + const packageVersion = packageJson.version; + const parsedPackagedVersion = semver.parse( packageJson.version ); + const isPackageVersionRC = parsedPackagedVersion.prerelease.length > 0; + + // Are we going to release an RC? + if ( isRC ) { + // We are releasing an RC. + // If packageVersion is stable, then generate new branch and RC1. + // If packageVersion is RC, then checkout branch and inc RC. + if ( ! isPackageVersionRC ) { + version = getNextMajorVersion( packageVersion ) + '-rc.1'; + const parsedVersion = semver.parse( version ); + + releaseBranch = + 'release/' + parsedVersion.major + '.' + parsedVersion.minor; + + await runReleaseBranchCreationStep( gitWorkingDirectory, - abortMessage - ) - : await runReleaseBranchCheckoutStep( + abortMessage, + version, + releaseBranch + ); + } else { + version = semver.inc( packageVersion, 'prerelease', 'rc' ); + releaseBranch = findReleaseBranchName( packageJsonPath ); + + await runReleaseBranchCheckoutStep( gitWorkingDirectory, - abortMessage - ); + abortMessage, + version, + releaseBranch + ); + } + } else { + // We are releasing a stable version. + // If packageVersion is stable, then checkout the branch and inc patch. + // If packageVersion is RC, then checkout the branch and inc patch, effectively removing the RC. + version = semver.inc( packageVersion, 'patch' ); + releaseBranch = findReleaseBranchName( packageJsonPath ); + + await runReleaseBranchCheckoutStep( + gitWorkingDirectory, + abortMessage, + version, + findReleaseBranchName( packageJsonPath ) + ); + } + + const versionLabel = version.replace( /\-rc\.([0-9]+)/, ' RC$1' ); if ( ! ( await isMilestoneClear( version ) ) ) { await askForConfirmation( diff --git a/bin/plugin/commands/test/common.js b/bin/plugin/commands/test/common.js new file mode 100644 index 00000000000000..e3a2afafe63c9d --- /dev/null +++ b/bin/plugin/commands/test/common.js @@ -0,0 +1,76 @@ +/** + * Internal dependencies + */ +import { calculateVersionBumpFromChangelog } from '../common'; + +describe( 'calculateVersionBumpFromChangelog', () => { + it( 'should return null when no lines provided', () => { + expect( calculateVersionBumpFromChangelog( [] ) ).toBe( null ); + } ); + + it( 'should return null when no unreleased header found', () => { + expect( + calculateVersionBumpFromChangelog( [ + 'First line', + 'Second line', + 'Third line', + 'Fourth line', + 'Fifth line', + ] ) + ).toBe( null ); + } ); + + it( 'should return null when Unreleased header found but no entries', () => { + expect( + calculateVersionBumpFromChangelog( [ + 'First line', + '## Unreleased', + 'Third line', + 'Fourth line', + 'Fifth line', + ] ) + ).toBe( null ); + } ); + + it( 'should return patch version Unreleased header and new item detected', () => { + expect( + calculateVersionBumpFromChangelog( [ + 'First line', + '## Unreleased', + 'Third line', + ' - new item added', + 'Fifth line', + ] ) + ).toBe( 'patch' ); + } ); + + it( 'should return enforce higher version bump when new item detected with lower level', () => { + expect( + calculateVersionBumpFromChangelog( + [ + 'First line', + '## Unreleased', + 'Third line', + ' - new item added', + 'Fifth line', + ], + 'major' + ) + ).toBe( 'major' ); + } ); + + it( 'should return major version bump when breaking changes detected', () => { + expect( + calculateVersionBumpFromChangelog( + [ + 'First line', + '## Unreleased', + '### Breaking Changes', + ' - new item added', + 'Fifth line', + ], + 'major' + ) + ).toBe( 'major' ); + } ); +} ); diff --git a/bin/unit-test-date.sh b/bin/unit-test-date.sh new file mode 100755 index 00000000000000..d3e5c79bba83df --- /dev/null +++ b/bin/unit-test-date.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -eu + +pids=() +pidsTimezones=() +pidsLocales=() + +timezones=(EST GMT CET) +locales=(en_US ja_JP) + +for timezone in "${timezones[@]}"; do + for locale in "${locales[@]}"; do + TZ=$timezone LANG=$locale npm run test-unit -- packages/date "$@" & + pids+=($!) + pidsTimezones+=($timezone) + pidsLocales+=($locale) + done +done + +for i in "${!pids[@]}"; do + pid=${pids[i]} + timezone=${pidsTimezones[i]} + locale=${pidsLocales[i]} + wait "$pid" || ( + echo "Date tests failed with timezone = $timezone and locale = $locale" + exit 1 + ) +done diff --git a/changelog.txt b/changelog.txt index cd5451c5779ac5..a3130d3329dfca 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,269 +1,787 @@ == Changelog == -= 9.2.0-rc.1 = += 9.6.0-rc.1 = -### Features +Changelog creation in progress. -- Widgets Screen: - - Add legacy widget inspector card component. ([26142](https://github.com/WordPress/gutenberg/pull/26142)) - - Show the legacy widget name in list view. ([26138](https://github.com/WordPress/gutenberg/pull/26138)) - - Add unsaved changes warning to widgets screen. ([26081](https://github.com/WordPress/gutenberg/pull/26081)) - - Display Widget Area's name and description in the sidebar. ([25943](https://github.com/WordPress/gutenberg/pull/25943)) - - Widgets editor: Add basic options for extensibility. ([25758](https://github.com/WordPress/gutenberg/pull/25758)) - - Disallow multiple instances of reference widgets. ([26148](https://github.com/WordPress/gutenberg/pull/26148)) - - Embed widget type. ([26093](https://github.com/WordPress/gutenberg/pull/26093)) - - Add widget type endpoint. ([26042](https://github.com/WordPress/gutenberg/pull/26042)) - - Make edit-widgets package public. ([26016](https://github.com/WordPress/gutenberg/pull/26016)) - - Uncollapse widget area when block is dragged over. ([25992](https://github.com/WordPress/gutenberg/pull/25992)) - - Add meaningful labels for the Widgets screen ARIA landmarks. ([25867](https://github.com/WordPress/gutenberg/pull/25867)) - - Load custom block assets. ([25826](https://github.com/WordPress/gutenberg/pull/25826)) - - Test for storing raw html in widgets. ([24886](https://github.com/WordPress/gutenberg/pull/24886)) - -- Query Block: - - Add Custom Post Types support in Query block. ([25903](https://github.com/WordPress/gutenberg/pull/25903)) - - Set focus on Query block on insertion. ([26267](https://github.com/WordPress/gutenberg/pull/26267)) - - Add loading message to Query block while fetching results. ([26199](https://github.com/WordPress/gutenberg/pull/26199)) - - Add no results placeholder in Query block. ([25984](https://github.com/WordPress/gutenberg/pull/25984)) - -- Add video tracks functionality. ([25861](https://github.com/WordPress/gutenberg/pull/25861)) -- Transform multiple selected blocks to Columns block. ([25829](https://github.com/WordPress/gutenberg/pull/25829)) -- Add option to make Post Featured Image a link. ([25714](https://github.com/WordPress/gutenberg/pull/25714)) += 9.5.2 = + +### Bug Fixes: + +- Fix uncaught error with a custom generic store without a unsubscribe function in useSelect. + + += 9.5.1 = + +### Bug Fixes: + + - Revert date changes from branch 'replace-moment. + - Popover: Fix issue with undefined getBoundingClientRect. + - Fallback to regular subscribe if the store doesn't exist in useSelect. + + += 9.5.0 = + +### Features +- Full Height Alignment control: Implementation and Cover block integration. ([26615](https://github.com/WordPress/gutenberg/pull/26615)) +- Code block: Add support for font sizes. ([27294](https://github.com/WordPress/gutenberg/pull/27294)) ### Enhancements +- Improve block patterns preview in the Inserter. ([27204](https://github.com/WordPress/gutenberg/pull/27204)) +- Enhance social links placeholder to look more like the end result. ([26953](https://github.com/WordPress/gutenberg/pull/26953)) +- Add labels to image zoom control. ([24574](https://github.com/WordPress/gutenberg/pull/24574)) + +### New APIs + +- Data: Use store instance as param for select and dispatch. ([26655](https://github.com/WordPress/gutenberg/pull/26655)) +- Adds instance URL to wp-env start. ([27282](https://github.com/WordPress/gutenberg/pull/27282)) -- Add dropdown button to view templates in sidebar. ([26132](https://github.com/WordPress/gutenberg/pull/26132)) -- Gallery block: Use image caption as fallback for alt text. ([26082](https://github.com/WordPress/gutenberg/pull/26082)) -- Table block: Use hooks + API v2. ([26065](https://github.com/WordPress/gutenberg/pull/26065)) -- Refactor document actions to handle template part titles. ([26043](https://github.com/WordPress/gutenberg/pull/26043)) -- Info panel design force wrapping. ([26017](https://github.com/WordPress/gutenberg/pull/26017)) -- Cover: Add repeated background option. ([26001](https://github.com/WordPress/gutenberg/pull/26001)) -- Remove non-core blocks from default editor content. ([25844](https://github.com/WordPress/gutenberg/pull/25844)) -- Bring the block-based theme tutorial up to date. ([25830](https://github.com/WordPress/gutenberg/pull/25830)) -- Edit: Pass editor features dynamically. ([25795](https://github.com/WordPress/gutenberg/pull/25795)) -- Add very basic template information dropdown. ([25757](https://github.com/WordPress/gutenberg/pull/25757)) -- Update Block Based Themes Documentation. ([25710](https://github.com/WordPress/gutenberg/pull/25710)) -- Rename "Options" modal to "Preferences". ([25683](https://github.com/WordPress/gutenberg/pull/25683)) -- Add single column functionality to the Columns block. ([24065](https://github.com/WordPress/gutenberg/pull/24065)) -- Add more writing flow options: Reduced UI, theme styles, spotlight. ([22494](https://github.com/WordPress/gutenberg/pull/22494)) +### Bug Fixes + +- Fix crash when null date passed to TimePicker. ([27316](https://github.com/WordPress/gutenberg/pull/27316)) +- Add backward compatibility support for lightBlockWrapper in getSaveElement. ([27189](https://github.com/WordPress/gutenberg/pull/27189)) +- Restore the gray background in Post Editor. ([27188](https://github.com/WordPress/gutenberg/pull/27188)) +- Font size picker bug that adds px units to empty string values. ([27111](https://github.com/WordPress/gutenberg/pull/27111)) +- Font size unit back-compatibility does not executes on post edit. ([27106](https://github.com/WordPress/gutenberg/pull/27106)) +- Drop zone: Fix infinite loop in some contexts. ([27090](https://github.com/WordPress/gutenberg/pull/27090)) +- Interface regions: Fix focus style (on click). ([27074](https://github.com/WordPress/gutenberg/pull/27074)) +- Fix Separator editor styles. ([27071](https://github.com/WordPress/gutenberg/pull/27071)) +- Fix custom spacing support. ([27045](https://github.com/WordPress/gutenberg/pull/27045)) +- Fix jest process hanging. ([27008](https://github.com/WordPress/gutenberg/pull/27008)) +- Fix combobox suggestion list closure when clicking scrollbar. ([27367](https://github.com/WordPress/gutenberg/pull/27367)) +- Constrain tabbing to the popover in media replace flow. ([26939](https://github.com/WordPress/gutenberg/pull/26939)) +- Fix RangeControl mark placement and cursor styles. ([26745](https://github.com/WordPress/gutenberg/pull/26745)) +- New authors dropdown breaks author selection for editors. ([26554](https://github.com/WordPress/gutenberg/pull/26554)) +- Hooks: Use own instance's `doAction` for built-in hooks. ([26498](https://github.com/WordPress/gutenberg/pull/26498)) +- Inserter: Show preview in search results. ([27193](https://github.com/WordPress/gutenberg/pull/27193)) +- Gallery block: + - Fix duplicate css class. ([27311](https://github.com/WordPress/gutenberg/pull/27311)) + - Adds back in icon and title for gallery block. ([27293](https://github.com/WordPress/gutenberg/pull/27293)) +- Search block: Fix icon strokeWidth properties. ([27308](https://github.com/WordPress/gutenberg/pull/27308)) +- Image block: + - Fix the zoom slider width. ([27285](https://github.com/WordPress/gutenberg/pull/27285)) + - Fix double paste from clipboard. ([27199](https://github.com/WordPress/gutenberg/pull/27199)) + - Hide some controls on multi selection of Image blocks. ([27105](https://github.com/WordPress/gutenberg/pull/27105)) +- Columns: Align single half width column to left. ([27142](https://github.com/WordPress/gutenberg/pull/27142)) + +### Performance + +- Minimize the calls in useSelect by subscribing to only the stores needed. ([26724](https://github.com/WordPress/gutenberg/pull/26724)) +- Update prefer lang constructs to functions. ([27070](https://github.com/WordPress/gutenberg/pull/27070)) + +### Experiments + +- Full Site Editing Framework: + - Fix template resolution priorities. ([27303](https://github.com/WordPress/gutenberg/pull/27303)) + - Update default templates. ([26941](https://github.com/WordPress/gutenberg/pull/26941)) + - Add Support for Templates Default and Custom Titles and Descriptions (JS side - [27038](https://github.com/WordPress/gutenberg/pull/27038), PHP side [27036](https://github.com/WordPress/gutenberg/pull/27036)) + - Add theme taxonomy to templates and template parts. ([27016](https://github.com/WordPress/gutenberg/pull/27016)) +- Full Site Editing Blocks: + - Make the post title block editable. ([27240](https://github.com/WordPress/gutenberg/pull/27240)) + - Post Comment: Update placeholder. ([27013](https://github.com/WordPress/gutenberg/pull/27013)) + - Enable Post Featured Image to be set and replaced. ([27224](https://github.com/WordPress/gutenberg/pull/27224)) + - Add alignment controls to Post Featured Image block. ([27076](https://github.com/WordPress/gutenberg/pull/27076)) + - Query block: + - Add grid view. ([27067](https://github.com/WordPress/gutenberg/pull/27067)) + - Add Posts List variation. ([26990](https://github.com/WordPress/gutenberg/pull/26990)) + - New settings icon in block toolbar. ([27057](https://github.com/WordPress/gutenberg/pull/27057)) + - Fetch all available post types (#27049). ([27056](https://github.com/WordPress/gutenberg/pull/27056)) + - Update Query block's icon. ([27048](https://github.com/WordPress/gutenberg/pull/27048)) + - Query and QueryLoop use useInnerBlocksProps. ([27014](https://github.com/WordPress/gutenberg/pull/27014)) + +- Site Editor: + - Avoid throwing warnings if there are no terms for a template or template part. ([27210](https://github.com/WordPress/gutenberg/pull/27210)) + - Replace adminbar customize link with site-editor in FSE themes. ([27135](https://github.com/WordPress/gutenberg/pull/27135)) + - Update the new templates dropdown list. ([27235](https://github.com/WordPress/gutenberg/pull/27235)) + - Remove .block-editor selector dependency. ([27063](https://github.com/WordPress/gutenberg/pull/27063)) + - Remove unused FullscreenModeClose component. ([26997](https://github.com/WordPress/gutenberg/pull/26997)) + - Navigation: + - Fix item color and padding. ([27096](https://github.com/WordPress/gutenberg/pull/27096)) + - Hide empty menus. ([27141](https://github.com/WordPress/gutenberg/pull/27141)) + - Add isText prop to NavigationItem. ([27003](https://github.com/WordPress/gutenberg/pull/27003)) + - Handle the no search results state. ([27160](https://github.com/WordPress/gutenberg/pull/27160)) + - Add search to templates and template parts. ([26665](https://github.com/WordPress/gutenberg/pull/26665)) + - Hide navigation item if target menu is empty. ([25746](https://github.com/WordPress/gutenberg/pull/25746)) +- Global Styles: + - Sort Global Styles block panels by panel title. ([27163](https://github.com/WordPress/gutenberg/pull/27163)) + - Font Appearance should be enabled globally. ([27150](https://github.com/WordPress/gutenberg/pull/27150)) + - Restrict edition of theme colors by users. ([27250](https://github.com/WordPress/gutenberg/pull/27250)) + - Update mechanism that resolves Global Styles data. ([27237](https://github.com/WordPress/gutenberg/pull/27237)) + - Include px units on default font sizes defined on theme.json. ([27083](https://github.com/WordPress/gutenberg/pull/27083)) + - Update stylesheet generation at edit site. ([27065](https://github.com/WordPress/gutenberg/pull/27065)) + - Add button to reset color palette. ([26975](https://github.com/WordPress/gutenberg/pull/26975)) + - Abstract preset variable retrieving and setting. ([26970](https://github.com/WordPress/gutenberg/pull/26970)) + - Update metadata and add support for padding. ([27099](https://github.com/WordPress/gutenberg/pull/27099)) +- Templates and Template Parts wp-admin lists: + - Update the template parts admin list with new columns and views. ([27156](https://github.com/WordPress/gutenberg/pull/27156)) + - Add the theme source to the templates wp-admin list. ([27108](https://github.com/WordPress/gutenberg/pull/27108)) + - Extend the wp_template admin list with new views and columns. ([27034](https://github.com/WordPress/gutenberg/pull/27034)) + +- Support registry inheritance with atomic stores. ([27162](https://github.com/WordPress/gutenberg/pull/27162)) +- Code block: Paste plain text. ([27236](https://github.com/WordPress/gutenberg/pull/27236)) +- Generalize the atom family concept as an atom selector concept instead. ([27147](https://github.com/WordPress/gutenberg/pull/27147)) +- Bugs: + - Query block: Fix dirtying post on load. ([27323](https://github.com/WordPress/gutenberg/pull/27323)) + - Preserve 'Your homepage displays' settings when updating the 'general' settings. ([27206](https://github.com/WordPress/gutenberg/pull/27206)) + - Make sure templates and parts queries filter by tax_query. ([27113](https://github.com/WordPress/gutenberg/pull/27113)) + - Fix Template Part Not Found message on Windows server. ([26772](https://github.com/WordPress/gutenberg/pull/26772)) + - Respect filtered settings when they're ported to theme.json format. ([27010](https://github.com/WordPress/gutenberg/pull/27010)) + - Preset controls need the preset CSS variables in scope. ([27119](https://github.com/WordPress/gutenberg/pull/27119)) + - Site Editor: + - Show document title on small screens with nav sidebar open. ([27051](https://github.com/WordPress/gutenberg/pull/27051)) + - Fix block toolbar positioning. ([27266](https://github.com/WordPress/gutenberg/pull/27266)) + - Fix app header on small-medium screens. ([27310](https://github.com/WordPress/gutenberg/pull/27310)) + +### Documentation + +- Expand on the Deprecations documentation. ([27286](https://github.com/WordPress/gutenberg/pull/27286)) +- Publish MainDashboardButton documentation to handbook. ([27317](https://github.com/WordPress/gutenberg/pull/27317)) +- Update: Creating a block-based theme tutorial. ([27257](https://github.com/WordPress/gutenberg/pull/27257)) +- ESLint Plugin: Include a note about the minimum version required. ([27203](https://github.com/WordPress/gutenberg/pull/27203)) +- Docs: Update `@wordpress/data` README with API changes. ([27180](https://github.com/WordPress/gutenberg/pull/27180)) +- Data: Improve documentation for new API added around stores. ([27061](https://github.com/WordPress/gutenberg/pull/27061)) +- wp-env: Improve documentation for "run" command. ([27053](https://github.com/WordPress/gutenberg/pull/27053)) +- Code block: Update the documentation. ([27333](https://github.com/WordPress/gutenberg/pull/27333)) +- Add block toolbar component readme. ([25245](https://github.com/WordPress/gutenberg/pull/25245)) +- Add block patterns list component readme. ([24983](https://github.com/WordPress/gutenberg/pull/24983)) +- [Contributors docs] JS meetings were shifted to 15:00GMT. ([27047](https://github.com/WordPress/gutenberg/pull/27047)) +- Typos and tweaks: ([27120](https://github.com/WordPress/gutenberg/pull/27120), [27081](https://github.com/WordPress/gutenberg/pull/27081), [27062](https://github.com/WordPress/gutenberg/pull/27062), [27060](https://github.com/WordPress/gutenberg/pull/27060), [27039](https://github.com/WordPress/gutenberg/pull/27039), [27153](https://github.com/WordPress/gutenberg/pull/27153)) + +### Code Quality +- Replace store name string with exposed store definition: + - Edit Navigation ([27182](https://github.com/WordPress/gutenberg/pull/27182), [27281](https://github.com/WordPress/gutenberg/pull/27281)) + - Blocks ([27336](https://github.com/WordPress/gutenberg/pull/27336)) + - Reusable Blocks ([27094](https://github.com/WordPress/gutenberg/pull/27094)) + - Block Directory ([27178](https://github.com/WordPress/gutenberg/pull/27178)) + - Keyboard Shortcuts ([27355](https://github.com/WordPress/gutenberg/pull/27355)) +- Search block: Remove invalid prop which was causing a React warning. ([27306](https://github.com/WordPress/gutenberg/pull/27306)) +- Refactor click redirect to avoid trailing div. ([27253](https://github.com/WordPress/gutenberg/pull/27253)) + +- Editor styles: Convert to hook. ([27080](https://github.com/WordPress/gutenberg/pull/27080)) +- Add: PHP util function equivalent to lodash set. ([27077](https://github.com/WordPress/gutenberg/pull/27077)) +- Interface: Remove regions wrapper div. ([27066](https://github.com/WordPress/gutenberg/pull/27066)) +- Refactor typing observer. ([27043](https://github.com/WordPress/gutenberg/pull/27043)) +- Visual editor: Remove 4 wrapper divs. ([27035](https://github.com/WordPress/gutenberg/pull/27035)) +- Insertion point: Avoid wrapper div. ([26994](https://github.com/WordPress/gutenberg/pull/26994)) +- Typewriter: Rewrite with hooks. ([26986](https://github.com/WordPress/gutenberg/pull/26986)) +- Deprecate the withGlobalEvents HoC. ([26749](https://github.com/WordPress/gutenberg/pull/26749)) +- Edit Post: Refactor effects to action generators. ([27069](https://github.com/WordPress/gutenberg/pull/27069)) + +### Build Tooling + +- Add eslint rule to prohibit unsafe APIs. ([27301](https://github.com/WordPress/gutenberg/pull/27301)) +- Update package lock to fix CI failures. ([27098](https://github.com/WordPress/gutenberg/pull/27098), [27102](https://github.com/WordPress/gutenberg/pull/27102)) +- GitHub Actions: Build Plugin zip, store as artifact on every PR. ([26746](https://github.com/WordPress/gutenberg/pull/26746)) +- Scripts: Unpin ignore-emit-webpack-plugin dependency. ([26739](https://github.com/WordPress/gutenberg/pull/26739)) +- Adds Support to wp-env for setting the PHP version. ([25268](https://github.com/WordPress/gutenberg/pull/25268)) +- Configure phpunit-watcher to improve devex. ([27058](https://github.com/WordPress/gutenberg/pull/27058)) +- Run phpunit even when phpcs fails. ([27024](https://github.com/WordPress/gutenberg/pull/27024)) +- Scripts: Auto format TypeScript files with format-js. ([27138](https://github.com/WordPress/gutenberg/pull/27138)) +- End 2 End Tests: + - Fix randomly failing end-to-end test. ([27358](https://github.com/WordPress/gutenberg/pull/27358)) + - Fix multi entity editing test. ([27347](https://github.com/WordPress/gutenberg/pull/27347)) + - Add end-to-end tests for image editing tools. ([27262](https://github.com/WordPress/gutenberg/pull/27262)) + +### Various + +- Simplify CSS for the code block. ([27314](https://github.com/WordPress/gutenberg/pull/27314)) +- Update the block variation widths in the block placeholder. ([27255](https://github.com/WordPress/gutenberg/pull/27255)) +- Don’t split translatable strings in block templates. ([27361](https://github.com/WordPress/gutenberg/pull/27361)) +- Reduce margin on placeholder for media. ([27252](https://github.com/WordPress/gutenberg/pull/27252)) +- Interface package: Move MainDashboardButton slot. ([27213](https://github.com/WordPress/gutenberg/pull/27213)) +- Decrease scrim when in a modal overlay. ([27054](https://github.com/WordPress/gutenberg/pull/27054)) +- Replace 'Remove from Reusable blocks' with 'Manage Reusable blocks'. ([27026](https://github.com/WordPress/gutenberg/pull/27026)) +- Mark AWAIT_PROMISE as unstable API. ([26852](https://github.com/WordPress/gutenberg/pull/26852)) +- Block Support: Separate opt in for font style and weight options. ([26844](https://github.com/WordPress/gutenberg/pull/26844)) +- Latest Posts: Don't use `target="_blank"`. ([25730](https://github.com/WordPress/gutenberg/pull/25730)) + + += 9.4.1 = + + + += 9.4.0 = + +### Features + +- Add Keyboard Input inline format. ([26801](https://github.com/WordPress/gutenberg/pull/26801)) +- Add Block variations transformations. ([26687](https://github.com/WordPress/gutenberg/pull/26687)) +- Add width selector for button block. ([25999](https://github.com/WordPress/gutenberg/pull/25999)) ([26781](https://github.com/WordPress/gutenberg/pull/26781)). +- Add font size support to the List block. ([26257](https://github.com/WordPress/gutenberg/pull/26257)) +- Social Links: Add ability to change social icon sizes. ([25921](https://github.com/WordPress/gutenberg/pull/25921)) + +### Enhancements + +- Unify the inserter search UI. ([26595](https://github.com/WordPress/gutenberg/pull/26595)) +- Polish custom select menu styles. ([26956](https://github.com/WordPress/gutenberg/pull/26956)) +- Polish menu item styles. ([26889](https://github.com/WordPress/gutenberg/pull/26889)) ([26720](https://github.com/WordPress/gutenberg/pull/26720)) ([25218](https://github.com/WordPress/gutenberg/pull/25218)) ([26572](https://github.com/WordPress/gutenberg/pull/26572)) +- Polish the link interface. ([26551](https://github.com/WordPress/gutenberg/pull/26551)) +- Code & Preformatted Blocks: Delete on backspace if empty. ([26605](https://github.com/WordPress/gutenberg/pull/26605)) +- File Block: Move the URL button to the Block toolbar. ([26602](https://github.com/WordPress/gutenberg/pull/26602)) +- Allow adding a header by typing /h1, /h2 etc. ([26597](https://github.com/WordPress/gutenberg/pull/26597)) +- Polish Shortcode block styling. ([26456](https://github.com/WordPress/gutenberg/pull/26456)) +- Polish Search block styling. ([26446](https://github.com/WordPress/gutenberg/pull/26446)) +- Use a wider canvas for themes that don't provide a custom width. ([26357](https://github.com/WordPress/gutenberg/pull/26357)) +- Show reduced UI on hover. ([26882](https://github.com/WordPress/gutenberg/pull/26882)) +- Update Legacy Widget toolbar button font to match UI when displayed in Widget Screen. ([26841](https://github.com/WordPress/gutenberg/pull/26841)) +- Update token field icon. ([26726](https://github.com/WordPress/gutenberg/pull/26726)) +- Windows 10 high contrast mode improvements. ([26567](https://github.com/WordPress/gutenberg/pull/26567)) ### New APIs -- Make block supports server-side explicit. ([26192](https://github.com/WordPress/gutenberg/pull/26192)) -- New hook: UseDebounce for speak function. ([25948](https://github.com/WordPress/gutenberg/pull/25948)) -- Make the custom spacing theme support flag and block support API stable. ([25788](https://github.com/WordPress/gutenberg/pull/25788)) -- Add/line height support flag stable. ([25769](https://github.com/WordPress/gutenberg/pull/25769)) -- Mark the font size support flag as stable. ([25695](https://github.com/WordPress/gutenberg/pull/25695)) -- Mark the color support flag as stable. ([25694](https://github.com/WordPress/gutenberg/pull/25694)) -- Add a button to allow resetting the ComboboxControl value. ([25692](https://github.com/WordPress/gutenberg/pull/25692)) -- Block API: Light block edit/save symmetry. ([25644](https://github.com/WordPress/gutenberg/pull/25644)) -- Block API: Stabilize light block hook. ([25642](https://github.com/WordPress/gutenberg/pull/25642)) -- Inner blocks: Try hook approach. ([25633](https://github.com/WordPress/gutenberg/pull/25633)) -- API: Stabilize localAutosave() as autosave( { local: True } ). ([20149](https://github.com/WordPress/gutenberg/pull/20149)) +- Create block: Add support for external templates installed from npm. ([23712](https://github.com/WordPress/gutenberg/pull/23712)) +- Add optional icon to snackbar notices. ([26907](https://github.com/WordPress/gutenberg/pull/26907)) +- @wordpress/env: Add support for custom WP_HOME port. ([26507](https://github.com/WordPress/gutenberg/pull/26507)) +- @wordpress/lazy-import: Allow importing local paths. ([23751](https://github.com/WordPress/gutenberg/pull/23751)) +- Format library: Introduce useAnchorRef. ([26782](https://github.com/WordPress/gutenberg/pull/26782)) ### Bug Fixes -- Fix for current_parsed_blocks value when block has inner blocks. ([26291](https://github.com/WordPress/gutenberg/pull/26291)) -- Fix updating clientId mapping. ([26290](https://github.com/WordPress/gutenberg/pull/26290)) -- Fix typo in wrapper attributes. ([26282](https://github.com/WordPress/gutenberg/pull/26282)) -- Fix: Keep the 'Insert from URL' entered value on ImagePlaceholder. ([26262](https://github.com/WordPress/gutenberg/pull/26262)) -- Fix align order in heading block. ([26260](https://github.com/WordPress/gutenberg/pull/26260)) -- Widgets screen: Add a filter function to `registerCoreBlock`. ([26259](https://github.com/WordPress/gutenberg/pull/26259)) -- Use ToolbarButtons instead of Buttons in the Legacy Widget block's toolbar. ([26258](https://github.com/WordPress/gutenberg/pull/26258)) -- Widgets screen: Add "Browse all" option to the inserter. ([26256](https://github.com/WordPress/gutenberg/pull/26256)) -- Fix: Post schedule label showing wrong time if site and user timezones did not match. ([26212](https://github.com/WordPress/gutenberg/pull/26212)) -- Fix Site Title block's heading levels appearance. ([26202](https://github.com/WordPress/gutenberg/pull/26202)) -- Writing flow: Fix in-between inserter for aligned blocks. ([26197](https://github.com/WordPress/gutenberg/pull/26197)) -- Fix Site Tagline block's text alignment. ([26191](https://github.com/WordPress/gutenberg/pull/26191)) -- Fix separator and spacer blocks after api v2 refactoring. ([26157](https://github.com/WordPress/gutenberg/pull/26157)) -- Global Styles sidebar (blocks tab): Protect against not registered blocks. ([26149](https://github.com/WordPress/gutenberg/pull/26149)) -- Block templates: Recognize and convert old or derivative block types to their canonical form. ([26147](https://github.com/WordPress/gutenberg/pull/26147)) -- Fix editing Legacy Widgets doesn't enable "Save" button. ([26144](https://github.com/WordPress/gutenberg/pull/26144)) -- Fix Cover width regression. ([26143](https://github.com/WordPress/gutenberg/pull/26143)) -- Fix tabbing in widgets not triggering auto-scrolling. ([26139](https://github.com/WordPress/gutenberg/pull/26139)) -- Cover block: Improve overlay opacity handling. ([26133](https://github.com/WordPress/gutenberg/pull/26133)) -- Fix icons type annotation. ([26129](https://github.com/WordPress/gutenberg/pull/26129)) -- FullscreenMode: Remove the is-fullscreen-mode CSS class from body on unmount. ([26103](https://github.com/WordPress/gutenberg/pull/26103)) -- Make sure Global Styles CPT includes a theme reference. ([26061](https://github.com/WordPress/gutenberg/pull/26061)) -- Restrict legacy widget block to only being a child of widget area. ([26053](https://github.com/WordPress/gutenberg/pull/26053)) -- Fix/wrong classes search block. ([26052](https://github.com/WordPress/gutenberg/pull/26052)) -- Fix drag and drop in empty widget area. ([26051](https://github.com/WordPress/gutenberg/pull/26051)) -- Fix unit tests by updating cover block fixtures. ([26044](https://github.com/WordPress/gutenberg/pull/26044)) -- Upgrade autoprefixer to fix fit-content in firefox. ([26019](https://github.com/WordPress/gutenberg/pull/26019)) -- Fix widget area title font. ([26018](https://github.com/WordPress/gutenberg/pull/26018)) -- Widgets screen: Fix WP Admin Bar Widgets screen link. ([26015](https://github.com/WordPress/gutenberg/pull/26015)) -- Hide parent selector in widget area. ([26011](https://github.com/WordPress/gutenberg/pull/26011)) -- Document Actions: Fix unexpected label wrapping. ([26004](https://github.com/WordPress/gutenberg/pull/26004)) -- Fix template part theme identifier. ([25995](https://github.com/WordPress/gutenberg/pull/25995)) -- Show all widget areas on widget screen. ([25977](https://github.com/WordPress/gutenberg/pull/25977)) -- Fix block editor example in storybook. ([25976](https://github.com/WordPress/gutenberg/pull/25976)) -- Fix 9:16 aspect ratio styling. ([25972](https://github.com/WordPress/gutenberg/pull/25972)) -- Fix gallery caption not centered in the front-end issue. ([25962](https://github.com/WordPress/gutenberg/pull/25962)) -- Widgets screen: Add save keyboard shortcut. ([25944](https://github.com/WordPress/gutenberg/pull/25944)) -- Widgets screen: Remove default hover background in panel title. ([25939](https://github.com/WordPress/gutenberg/pull/25939)) -- Fix failing previews end-to-end test. ([25938](https://github.com/WordPress/gutenberg/pull/25938)) -- Fix: Input control drag and box control change. ([25933](https://github.com/WordPress/gutenberg/pull/25933)) -- Fix end-to-end tests related to template parts. ([25923](https://github.com/WordPress/gutenberg/pull/25923)) -- Fix insertion indicator margin. ([25893](https://github.com/WordPress/gutenberg/pull/25893)) -- Fix blue line indicator not showing at the end. ([25849](https://github.com/WordPress/gutenberg/pull/25849)) -- Fix incorrect attribute type specified in Search block. ([25813](https://github.com/WordPress/gutenberg/pull/25813)) -- Document Actions: Fix Block Editor Inserter Overlap with Document Titles. ([25801](https://github.com/WordPress/gutenberg/pull/25801)) -- Fix PHP warning in widget utils REST controller. ([25797](https://github.com/WordPress/gutenberg/pull/25797)) -- Include edit-widgets php files in build. ([25792](https://github.com/WordPress/gutenberg/pull/25792)) -- Docs: Fix typo in Git Workflow. ([25779](https://github.com/WordPress/gutenberg/pull/25779)) -- Widgets screen: Fix widget-area accessibility. ([25732](https://github.com/WordPress/gutenberg/pull/25732)) -- Widgets screen: Fix insertion point in widget areas. ([25727](https://github.com/WordPress/gutenberg/pull/25727)) -- Document Settings: Fix document title hover and select animations. ([25719](https://github.com/WordPress/gutenberg/pull/25719)) -- Define text color for warning message component. ([25713](https://github.com/WordPress/gutenberg/pull/25713)) -- RichText: Remove native props for web. ([25700](https://github.com/WordPress/gutenberg/pull/25700)) -- Use h3 in the legacy widget title. ([25690](https://github.com/WordPress/gutenberg/pull/25690)) -- Navigation block: Use unbounded query when requesting top level pages. ([25689](https://github.com/WordPress/gutenberg/pull/25689)) -- Document Actions: Fix document title misalignment with an open nav sidebar. ([25630](https://github.com/WordPress/gutenberg/pull/25630)) -- Fix React error in @wordpress/block-editor documentation usage example caused by applying args to setState call. ([25492](https://github.com/WordPress/gutenberg/pull/25492)) -- Cover Block: Show spinner while uploading. ([25401](https://github.com/WordPress/gutenberg/pull/25401)) -- Button block: Reduce chance of style conflicts. ([24919](https://github.com/WordPress/gutenberg/pull/24919)) -- Fix skipped shortcode transforms in raw handling. ([22840](https://github.com/WordPress/gutenberg/pull/22840)) -- Media links: Fix linking for images inserted from URL. ([22195](https://github.com/WordPress/gutenberg/pull/22195)) -- Refactor reusable block edit component using hooks (and fix interactions with multiple instances of the same reusable block). ([21427](https://github.com/WordPress/gutenberg/pull/21427)) +- Fix block context injection hook after Core change in block rendering. ([26998](https://github.com/WordPress/gutenberg/pull/26998)) ([27011](https://github.com/WordPress/gutenberg/pull/27011)) +- Fix Query block's Toolbar popover width. ([26992](https://github.com/WordPress/gutenberg/pull/26992)) +- Fix IE11 interface bugs. ([26944](https://github.com/WordPress/gutenberg/pull/26944)) +- Improve arrow navigation between blocks. ([26921](https://github.com/WordPress/gutenberg/pull/26921)) +- Fix block alignments when theme styles are disabled. ([26912](https://github.com/WordPress/gutenberg/pull/26912)) ([26680](https://github.com/WordPress/gutenberg/pull/26680)) ([26376](https://github.com/WordPress/gutenberg/pull/26376)) +- Fix padding of text-only buttons mode. ([26769](https://github.com/WordPress/gutenberg/pull/26769)) +- Fix horizontal mover icon position. ([26761](https://github.com/WordPress/gutenberg/pull/26761)) +- Fix column width units. ([26757](https://github.com/WordPress/gutenberg/pull/26757)) +- FormTokenField input position when typing. ([26741](https://github.com/WordPress/gutenberg/pull/26741)) +- Fix applying colors to outlined buttons. ([26707](https://github.com/WordPress/gutenberg/pull/26707)) ([24626](https://github.com/WordPress/gutenberg/pull/24626)) +- Render big font sizes properly in the font size picker. ([26705](https://github.com/WordPress/gutenberg/pull/26705)) +- Use currentColor in quote blocks to better support dark themes. ([26684](https://github.com/WordPress/gutenberg/pull/26684)) +- Fix "Browse All" in Quick Inserter for container blocks. ([26443](https://github.com/WordPress/gutenberg/pull/26443)) +- Fix responsive embeds in the widget screen. ([26263](https://github.com/WordPress/gutenberg/pull/26263)) +- Fix undefined index notice in Social Link Block. ([25663](https://github.com/WordPress/gutenberg/pull/25663)) +- Buttons block: Fix default alignment icon in toolbar to reflect the actual default alignment of buttons. ([26910](https://github.com/WordPress/gutenberg/pull/26910)) +- Block Support: Fix font size style when applying block support. ([26762](https://github.com/WordPress/gutenberg/pull/26762)) +- Do not invalidate the entity record cache during optimistic update in saveEntityRecord. ([26627](https://github.com/WordPress/gutenberg/pull/26627)) +- Provide a minimum of code wrapping for the code block. ([26623](https://github.com/WordPress/gutenberg/pull/26623)) + +### Experiments + +- Full Site Editing Framework: Rework templates and template parts synchronization. ([26650](https://github.com/WordPress/gutenberg/pull/26650)) ([26383](https://github.com/WordPress/gutenberg/pull/26383)) +- Full Site Editing Blocks: + - Post Excerpt block: Allow editing of generated excerpts. ([26637](https://github.com/WordPress/gutenberg/pull/26637)) + - Post Excerpt block: Add missing closing div tag. ([26806](https://github.com/WordPress/gutenberg/pull/26806)) + - Add wide and full alignment options to the post/site title blocks. ([26601](https://github.com/WordPress/gutenberg/pull/26601)) + - Template Part block: Render preview as div. ([26873](https://github.com/WordPress/gutenberg/pull/26873)) + - Post Taxonomies: Fix PHP Fatal erroor for unregistered taxonomy (#26851). ([26854](https://github.com/WordPress/gutenberg/pull/26854)) + - PostAuthor: Safeguard to wait for authors to load. ([26776](https://github.com/WordPress/gutenberg/pull/26776)) + - Query block: Update the order of settings and filters in the sidebar. ([26647](https://github.com/WordPress/gutenberg/pull/26647)) +- Site Editor: + - Fix responsiveness. ([26021](https://github.com/WordPress/gutenberg/pull/26021)) + - Fix alignment of the new template button. ([26934](https://github.com/WordPress/gutenberg/pull/26934)) + - Add default block widths styles. ([26853](https://github.com/WordPress/gutenberg/pull/26853)) + - Refactor menu creation code. ([26966](https://github.com/WordPress/gutenberg/pull/26966)) + - Only show auto-draft template parts corresponding to current theme. ([26948](https://github.com/WordPress/gutenberg/pull/26948)) + - Polish the template navigation menu. ([26933](https://github.com/WordPress/gutenberg/pull/26933)), ([26930](https://github.com/WordPress/gutenberg/pull/26930)). + - Add navigation panel back button slot. ([26846](https://github.com/WordPress/gutenberg/pull/26846)) + - Remove dashboard button focus on mount. ([26845](https://github.com/WordPress/gutenberg/pull/26845)) + - Saving flow: Use template and template parts entities titles. ([26708](https://github.com/WordPress/gutenberg/pull/26708)) ([26653](https://github.com/WordPress/gutenberg/pull/26653)) + - Reorder template creation dropdown. ([26610](https://github.com/WordPress/gutenberg/pull/26610)) +- Global styles: + - Fallback to theme color pallete. ([26783](https://github.com/WordPress/gutenberg/pull/26783)) ([26786](https://github.com/WordPress/gutenberg/pull/26786)) + - Hide Block panels without content. ([26609](https://github.com/WordPress/gutenberg/pull/26609)) + - Update styles to rely on CSS variables for colors and gradients. ([26319](https://github.com/WordPress/gutenberg/pull/26319)) + - Fix Table block global styles selector. ([26973](https://github.com/WordPress/gutenberg/pull/26973)) + - Hide the line height panel if disabled in theme.json file. ([26778](https://github.com/WordPress/gutenberg/pull/26778)) + - Cache generated styles. ([25680](https://github.com/WordPress/gutenberg/pull/25680)) + - Add: Mechanism to detect if a block instance matches a global styles selector. ([26945](https://github.com/WordPress/gutenberg/pull/26945)) ([26991](https://github.com/WordPress/gutenberg/pull/26991)) + - Extract theme json processor. ([26803](https://github.com/WordPress/gutenberg/pull/26803)) + - Add support for line height at the global level. ([26767](https://github.com/WordPress/gutenberg/pull/26767)) + - Add preset classes generation on the client side. ([26224](https://github.com/WordPress/gutenberg/pull/26224)) +- Navigation block: Fix color support declaration. ([26928](https://github.com/WordPress/gutenberg/pull/26928)) +- Popover: Add sticky boundary element prop. ([26728](https://github.com/WordPress/gutenberg/pull/26728)) +- Block Support: + - Add font style and weight options with combined UI. ([26444](https://github.com/WordPress/gutenberg/pull/26444)) ([26868](https://github.com/WordPress/gutenberg/pull/26868)) + - Add text transform block support flag. ([26060](https://github.com/WordPress/gutenberg/pull/26060)) ([26059](https://github.com/WordPress/gutenberg/pull/26059)) + - Add: Font Family picking mechanism. ([24868](https://github.com/WordPress/gutenberg/pull/24868)), ([26750](https://github.com/WordPress/gutenberg/pull/26750)), ([26759](https://github.com/WordPress/gutenberg/pull/26759)). + ### Performance -- Paragraph: Avoid selector to improve performance. ([26150](https://github.com/WordPress/gutenberg/pull/26150)) -- Remove transition on block selection indicator. ([25974](https://github.com/WordPress/gutenberg/pull/25974)) -- Widgets screen: Preload request to /sidebars. ([25726](https://github.com/WordPress/gutenberg/pull/25726)) +- Use 2-pass terser compression. ([24821](https://github.com/WordPress/gutenberg/pull/24821)) +- Memoize getEntityRecords to prevent infinite re-renders. ([26447](https://github.com/WordPress/gutenberg/pull/26447)) +- Resolve per-entity resolvers after receiving a list of records. ([26575](https://github.com/WordPress/gutenberg/pull/26575)) + +### Documentation + +- Update theme.json documentation with new properties added. ([26891](https://github.com/WordPress/gutenberg/pull/26891)) +- Interface: Add deprecation logic for leftSidebar prop. ([26826](https://github.com/WordPress/gutenberg/pull/26826)) +- Document the block supports style properties. ([26771](https://github.com/WordPress/gutenberg/pull/26771)) ([26931](https://github.com/WordPress/gutenberg/pull/26931)) ([26859](https://github.com/WordPress/gutenberg/pull/26859)) +- Update testing documentation with info about React Testing Library. ([23015](https://github.com/WordPress/gutenberg/pull/23015)) +- Add documentation for AutosaveMonitor. ([26663](https://github.com/WordPress/gutenberg/pull/26663)) +- Typos and tweaks: ([26797](https://github.com/WordPress/gutenberg/pull/26797)), ([26796](https://github.com/WordPress/gutenberg/pull/26796)), ([26795](https://github.com/WordPress/gutenberg/pull/26795)), ([26794](https://github.com/WordPress/gutenberg/pull/26794)), ([26793](https://github.com/WordPress/gutenberg/pull/26793)), ([26792](https://github.com/WordPress/gutenberg/pull/26792)), ([26791](https://github.com/WordPress/gutenberg/pull/26791)), ([26790](https://github.com/WordPress/gutenberg/pull/26790)), ([21607](https://github.com/WordPress/gutenberg/pull/21607)), ([27017](https://github.com/WordPress/gutenberg/pull/27017)), ([26919](https://github.com/WordPress/gutenberg/pull/26919)), ([26863](https://github.com/WordPress/gutenberg/pull/26863)), ([26857](https://github.com/WordPress/gutenberg/pull/26857)), ([26618](https://github.com/WordPress/gutenberg/pull/26618)), ([26029](https://github.com/WordPress/gutenberg/pull/26029)), ([25349](https://github.com/WordPress/gutenberg/pull/25349)), ([17878](https://github.com/WordPress/gutenberg/pull/17878)), ([16090](https://github.com/WordPress/gutenberg/pull/16090)), ([26799](https://github.com/WordPress/gutenberg/pull/26799)), ([26765](https://github.com/WordPress/gutenberg/pull/26765)), ([26654](https://github.com/WordPress/gutenberg/pull/26654)). + +### Code Quality + +- Use date-fns and date-fns-tz instead of moment. ([25782](https://github.com/WordPress/gutenberg/pull/25782)) ([27002](https://github.com/WordPress/gutenberg/pull/27002)) +- is-shallow-equal: Convert to ESM. ([26833](https://github.com/WordPress/gutenberg/pull/26833)) +- Minor code refactoring in template part previews. ([26949](https://github.com/WordPress/gutenberg/pull/26949)) +- Drop zone: Rewrite with hooks and simplify. ([26893](https://github.com/WordPress/gutenberg/pull/26893)) +- PostTitle: Rewrite with hooks (+ avoid globals). ([26820](https://github.com/WordPress/gutenberg/pull/26820)) +- Avoid relying on a the global document. ([26834](https://github.com/WordPress/gutenberg/pull/26834)) ([26814](https://github.com/WordPress/gutenberg/pull/26814)) ([26813](https://github.com/WordPress/gutenberg/pull/26813)) ([26657](https://github.com/WordPress/gutenberg/pull/26657)) +- Format library: Use hooks for all components. ([26779](https://github.com/WordPress/gutenberg/pull/26779)) +- Sandbox: Use hooks and avoid withGlobalEvents. ([26742](https://github.com/WordPress/gutenberg/pull/26742)) +- Draggable: Use hooks and prepare for iframe. ([26897](https://github.com/WordPress/gutenberg/pull/26897)) +- PostLockedModal: Use hooks and avoid withGlobalEvents. ([26743](https://github.com/WordPress/gutenberg/pull/26743)) +- WpEmbedPreview: Use hooks and avoid withGlobalEvents. ([26740](https://github.com/WordPress/gutenberg/pull/26740)) +- FocusableIframe: Use hooks and avoid withGlobalEvents. ([26737](https://github.com/WordPress/gutenberg/pull/26737)) +- Navigation Component: Update styles to reference grid spacing helper. ([26523](https://github.com/WordPress/gutenberg/pull/26523)) +- Hooks: Type package. ([26430](https://github.com/WordPress/gutenberg/pull/26430)) +- Deprecated: Type package. ([26429](https://github.com/WordPress/gutenberg/pull/26429)) +- Movers: Small positioning refactor. ([26353](https://github.com/WordPress/gutenberg/pull/26353)) +- Use useAnimate for all Animate component usage. ([26201](https://github.com/WordPress/gutenberg/pull/26201)) +- Gallery block: Use API v2. ([26145](https://github.com/WordPress/gutenberg/pull/26145)) +- Gallery: Remove obsolete deprecation entry. ([26736](https://github.com/WordPress/gutenberg/pull/26736)) +- Cleanup: ([24831](https://github.com/WordPress/gutenberg/pull/24831)), ([23598](https://github.com/WordPress/gutenberg/pull/23598)), ([27000](https://github.com/WordPress/gutenberg/pull/27000)). +- Remove `useMemo` in Query inspector controls. ([26658](https://github.com/WordPress/gutenberg/pull/26658)) + + +### Build Tooling + +- Add eslint rule to warn against using globals for addEventListener. ([26810](https://github.com/WordPress/gutenberg/pull/26810)) +- Update workflows to use Node 14.x. ([26835](https://github.com/WordPress/gutenberg/pull/26835)) +- Add command to format PHP files. ([26850](https://github.com/WordPress/gutenberg/pull/26850)) +- Build Tooling: Rebuild stylesheets when imported styles are modified. ([26649](https://github.com/WordPress/gutenberg/pull/26649)) +- Fix GH actions "cancel" step. ([27025](https://github.com/WordPress/gutenberg/pull/27025)) +- Run assign fixed issues and first time contributor label tasks for PRs from forks. ([26876](https://github.com/WordPress/gutenberg/pull/26876)) +- Update .nvmrc to use latest LTS. ([26855](https://github.com/WordPress/gutenberg/pull/26855)) +- Update nodegit to 0.27.0 in @wordpress/env. ([26712](https://github.com/WordPress/gutenberg/pull/26712)) +- Tests: Add fixture for Column deprecation. ([26774](https://github.com/WordPress/gutenberg/pull/26774)) +- Store screenshots of CI end-to-end failures as CI artifacts. ([26664](https://github.com/WordPress/gutenberg/pull/26664)) ([26957](https://github.com/WordPress/gutenberg/pull/26957)) +- End 2 End Tests: + - Fix improper assertion in template-part.test. ([26709](https://github.com/WordPress/gutenberg/pull/26709)) + - Fix RTL end-to-end tests. ([26508](https://github.com/WordPress/gutenberg/pull/26508)) + - Add regresion end-to-end test for the empty reusable block causing WSODs issue. ([26913](https://github.com/WordPress/gutenberg/pull/26913)) + - Add block drag and drop test. ([26869](https://github.com/WordPress/gutenberg/pull/26869)) ([26904](https://github.com/WordPress/gutenberg/pull/26904)) + - Add Delete on backspace from empty code/preformatted blocks test. ([26972](https://github.com/WordPress/gutenberg/pull/26972)) + - Merge end-to-end test relying on order into one. ([26883](https://github.com/WordPress/gutenberg/pull/26883)) + - Add template part conversion end-to-end tests. ([26788](https://github.com/WordPress/gutenberg/pull/26788)) + - Make the allowed blocks test more stable. ([26631](https://github.com/WordPress/gutenberg/pull/26631)) + - Add end-to-end test utils to install and activate themes. ([23685](https://github.com/WordPress/gutenberg/pull/23685)) +### Various + +- TextControl: Support forwarding refs. ([26209](https://github.com/WordPress/gutenberg/pull/26209)) +- Update gutenberg_is_fse_theme function to make it filterable. ([27021](https://github.com/WordPress/gutenberg/pull/27021)) +- Windows: Use wp-env instead of file location. ([26671](https://github.com/WordPress/gutenberg/pull/26671)) +- Site Editor: Rename left sidebar → secondary sidebar. ([26517](https://github.com/WordPress/gutenberg/pull/26517)) +- Add State locks for concurrency control to @wordpress/core-data. ([26389](https://github.com/WordPress/gutenberg/pull/26389)) ([26661](https://github.com/WordPress/gutenberg/pull/26661)) +- Avoid PHP warnings when Gutenberg plugin folder isn't writable. ([17671](https://github.com/WordPress/gutenberg/pull/17671)) + + + += 9.3.0 = + +### Enhancements + +- Support for aall units in Font Size presets. ([26475](https://github.com/WordPress/gutenberg/pula/26475)) +- Sort post formats alphabetically by translated name. ([26305](https://github.com/WordPress/gutenberg/pull/26305)) +- Ensure Alignment options are always rendered in the same order. ([26269](https://github.com/WordPress/gutenberg/pull/26269)) +- Buttons block: Overhaul alignment and justification controls. ([23168](https://github.com/WordPress/gutenberg/pull/23168)) +- a11y: Retain focus position when tabbing back to the block toolbar. ([25760](https://github.com/WordPress/gutenberg/pull/25760)) +- Dark mode UI enhancements. ([26483](https://github.com/WordPress/gutenberg/pull/26483)) ([26510](https://github.com/WordPress/gutenberg/pull/26510)) +- Social Links: Add Patreon, Telegram, and Tiktok icons. ([26118](https://github.com/WordPress/gutenberg/pull/26118)) + +### Bug Fixes + +- Fix Visual regression on the color palette editor. ([26614](https://github.com/WordPress/gutenberg/pull/26614)) +- @wordpress/scripts: Fix error in ignore-emit-webpack-plugin. ([26591](https://github.com/WordPress/gutenberg/pull/26591)) +- Cover block: Restore default overlay background. ([26569](https://github.com/WordPress/gutenberg/pull/26569)) ([26625](https://github.com/WordPress/gutenberg/pull/26625)) +- i18n: Fix incorrectly pluralised strings. ([26565](https://github.com/WordPress/gutenberg/pull/26565)) +- Limit the editor interface to max-width 100%. ([26552](https://github.com/WordPress/gutenberg/pull/26552)) +- Ensure editor footer remains at the bottom of the screen when navigating regions. ([26533](https://github.com/WordPress/gutenberg/pull/26533)) +- URLInput: Use debounce() instead of throttle(). ([26529](https://github.com/WordPress/gutenberg/pull/26529)) +- Heading Block: Fix double alignment controls in toolbar. ([26492](https://github.com/WordPress/gutenberg/pull/26492)) +- Fix Block preview vertical offset. ([26487](https://github.com/WordPress/gutenberg/pull/26487)) +- Reusable Blocks: Make the number retrieved from the API unlimited. ([26486](https://github.com/WordPress/gutenberg/pull/26486)) +- Fix editor error when an empty reusable block exist. ([26484](https://github.com/WordPress/gutenberg/pull/26484)) +- Latest Posts: Add missing classname. ([26477](https://github.com/WordPress/gutenberg/pull/26477)) +- Fix single column block display for smaller screens. ([26438](https://github.com/WordPress/gutenberg/pull/26438)) +- Turn off autocomplete for ComboboxControl components. ([26427](https://github.com/WordPress/gutenberg/pull/26427)) +- Fix parent post selector initial value and search. ([26397](https://github.com/WordPress/gutenberg/pull/26397)) +- Fix gallery block undo issue. ([26377](https://github.com/WordPress/gutenberg/pull/26377)) +- Fix spellings in Getting Started guide. ([26310](https://github.com/WordPress/gutenberg/pull/26310)) +- Fix embed blocks rendering in widget areas. ([26307](https://github.com/WordPress/gutenberg/pull/26307)) +- Fix design of color/gradient controls. ([26255](https://github.com/WordPress/gutenberg/pull/26255)) +- Fix drop zone indicators for non blocks. ([25986](https://github.com/WordPress/gutenberg/pull/25986)) +- Fix left and right alignments for video embeds. ([24847](https://github.com/WordPress/gutenberg/pull/24847)) +- Next Page block: Center text properly. ([26515](https://github.com/WordPress/gutenberg/pull/26515)) + +### New APIs + +- Allow text buttons in DropdownMenu. ([26425](https://github.com/WordPress/gutenberg/pull/26425)) +- Support custom viewportWidth in block previews (example). ([26346](https://github.com/WordPress/gutenberg/pull/26346)) +- Change updateSelection property to false for InnerBlocks. ([26312](https://github.com/WordPress/gutenberg/pull/26312)) + +### Experiments + +- Full Site Editing : + - Disable customizer and widgets screens. ([26594](https://github.com/WordPress/gutenberg/pull/26594)) + - Automatically enable FSE experiment. ([26500](https://github.com/WordPress/gutenberg/pull/26500)) + - Remove the demo templates. ([26419](https://github.com/WordPress/gutenberg/pull/26419)) + - Strip post ids from template part blocks on export. ([26268](https://github.com/WordPress/gutenberg/pull/26268)) + - Allow themes to live in a sub directory. ([26391](https://github.com/WordPress/gutenberg/pull/26391)) +- Introduce the layout prop to InnerBlocks. ([26380](https://github.com/WordPress/gutenberg/pull/26380)) +- Site Editor + - Add Dropdown to Create Generic Templates. ([26284](https://github.com/WordPress/gutenberg/pull/26284)) + - Prevent inserter overscroll. ([26432](https://github.com/WordPress/gutenberg/pull/26432)) ([26583](https://github.com/WordPress/gutenberg/pull/26583)) + - Fix dirty template and template parts on template creation. ([26560](https://github.com/WordPress/gutenberg/pull/26560)) + - Fix composite role warnings triggered by template part previews. ([26406](https://github.com/WordPress/gutenberg/pull/26406)) + - Reduce the Amount of Data Passed Through the Components Tree. ([26463](https://github.com/WordPress/gutenberg/pull/26463)) + - Add the option to convert a template part to regular blocks. ([26488](https://github.com/WordPress/gutenberg/pull/26488)) + - Fix Invisible Template Previews in the Sidebar. ([26424](https://github.com/WordPress/gutenberg/pull/26424)) + - Add convert to template part flow. ([20445](https://github.com/WordPress/gutenberg/pull/20445)) + - Fix custom template part theme meta. ([26587](https://github.com/WordPress/gutenberg/pull/26587)) +- Query block: + - Add initial variations. ([26378](https://github.com/WordPress/gutenberg/pull/26378)) + - Add sticky support. ([26279](https://github.com/WordPress/gutenberg/pull/26279)) +- Global Styles: + - Use block settings on the block panels. ([26218](https://github.com/WordPress/gutenberg/pull/26218)) + - Fix: Font size picker regression on edit site global styles. ([26603](https://github.com/WordPress/gutenberg/pull/26603)) + - Process settings only once. ([26330](https://github.com/WordPress/gutenberg/pull/26330)) +- Navigation Component: + - Add Support for RTL Languages. ([26334](https://github.com/WordPress/gutenberg/pull/26334)) + - Styling revisions. ([26338](https://github.com/WordPress/gutenberg/pull/26338)) + - Fix focus behavior when opening the panel. ([26296](https://github.com/WordPress/gutenberg/pull/26296)) + - Fix height of Navigation panel and make it scrollable. ([26187](https://github.com/WordPress/gutenberg/pull/26187)) + - Search Control in Menu Titles. ([25315](https://github.com/WordPress/gutenberg/pull/25315)) +- Use a DropdownMenu for menu selection in the navigation screen. ([25390](https://github.com/WordPress/gutenberg/pull/25390)) ### Documentation -- Add more CI status badges to README. ([26090](https://github.com/WordPress/gutenberg/pull/26090)) -- Docs: Getting started: MAMP: Add tip to fix WP-CLI. ([26057](https://github.com/WordPress/gutenberg/pull/26057)) -- Update colors readme with additional definitions. ([25954](https://github.com/WordPress/gutenberg/pull/25954)) -- Document isMultiBlock param for block transforms. ([25952](https://github.com/WordPress/gutenberg/pull/25952)) -- Update CI status badge in README. ([25907](https://github.com/WordPress/gutenberg/pull/25907)) -- Adds missing Curly brace. ([25748](https://github.com/WordPress/gutenberg/pull/25748)) -- Add documentation for colors component. ([25567](https://github.com/WordPress/gutenberg/pull/25567)) -- InspectorAdvancedControls: Add README.md. ([25566](https://github.com/WordPress/gutenberg/pull/25566)) -- Add documentation for useResizeCanvas. ([25558](https://github.com/WordPress/gutenberg/pull/25558)) -- Add/block navigation component readme. ([24882](https://github.com/WordPress/gutenberg/pull/24882)) +- Update glossary to include more block based terminology. ([26478](https://github.com/WordPress/gutenberg/pull/26478)) +- Update triage documentation for clarity around closing issues and labels. ([26480](https://github.com/WordPress/gutenberg/pull/26480)) +- Update WordPress versions document to include 5.6. ([26365](https://github.com/WordPress/gutenberg/pull/26365)) +- Typos and tweaks: ([26491](https://github.com/WordPress/gutenberg/pull/26491)), ([26553](https://github.com/WordPress/gutenberg/pull/26553)), ([26437](https://github.com/WordPress/gutenberg/pull/26437)), ([26400](https://github.com/WordPress/gutenberg/pull/26400)), ([26566](https://github.com/WordPress/gutenberg/pull/26566)). +- Storybook: Fix broken BlockDraggable story. ([26457](https://github.com/WordPress/gutenberg/pull/26457)) ([26431](https://github.com/WordPress/gutenberg/pull/26431)) ### Code Quality -- Pass all extra attributes down in get_block_wrapper_attributes. ([26280](https://github.com/WordPress/gutenberg/pull/26280)) -- Editor: Refactor PostFormatPanel to use React hooks. ([26273](https://github.com/WordPress/gutenberg/pull/26273)) -- BlockListBlock: Reduce passed props. ([26251](https://github.com/WordPress/gutenberg/pull/26251)) -- Editor: Refactor PostFormat to use React hooks. ([26238](https://github.com/WordPress/gutenberg/pull/26238)) -- Latest posts: Use hooks + API v2. ([26122](https://github.com/WordPress/gutenberg/pull/26122)) -- Latest comments: API v2. ([26113](https://github.com/WordPress/gutenberg/pull/26113)) -- Categories block: Use API v2. ([26112](https://github.com/WordPress/gutenberg/pull/26112)) -- Rename ReusableBlocksButtons to ReusableBlocksMenuItems. ([26099](https://github.com/WordPress/gutenberg/pull/26099)) -- Reusable block: Use API v2. ([26091](https://github.com/WordPress/gutenberg/pull/26091)) -- Gallery block: Use hooks. ([26088](https://github.com/WordPress/gutenberg/pull/26088)) -- Pullquote block: Use hooks + API v2. ([26068](https://github.com/WordPress/gutenberg/pull/26068)) -- Components: Start adding types progressively. ([26066](https://github.com/WordPress/gutenberg/pull/26066)) -- File block: Use hooks + API v2. ([26063](https://github.com/WordPress/gutenberg/pull/26063)) -- HTML block: Use hooks and API v2. ([26055](https://github.com/WordPress/gutenberg/pull/26055)) -- Update all blocks to API v2. ([26054](https://github.com/WordPress/gutenberg/pull/26054)) -- editor: Remove two unused registry controls. ([26048](https://github.com/WordPress/gutenberg/pull/26048)) -- Tweak styles of the document actions area. ([26038](https://github.com/WordPress/gutenberg/pull/26038)) -- Site Editor: Navigation panel replace hardcoded menu strings with constants. ([26026](https://github.com/WordPress/gutenberg/pull/26026)) -- Move left sidebar state to redux. ([26003](https://github.com/WordPress/gutenberg/pull/26003)) -- Refactor Categories to function component. ([25806](https://github.com/WordPress/gutenberg/pull/25806)) -- Classic block: Use hooks. ([25737](https://github.com/WordPress/gutenberg/pull/25737)) -- Remove animation from mover buttons. ([25728](https://github.com/WordPress/gutenberg/pull/25728)) -- Move widget-area to edit-widgets. ([25673](https://github.com/WordPress/gutenberg/pull/25673)) -- InnerBlocks: Add select dependencies. ([25672](https://github.com/WordPress/gutenberg/pull/25672)) -- Refactor Buttons block native edit component to use hooks. ([25636](https://github.com/WordPress/gutenberg/pull/25636)) -- Data: Build the basic data controls into every store. ([25362](https://github.com/WordPress/gutenberg/pull/25362)) -- Block Editor: Use optional chaining and nullish coalescing instead of Lodash.get. ([23632](https://github.com/WordPress/gutenberg/pull/23632)) -- Refactor Latest Comments block to use function component. ([23557](https://github.com/WordPress/gutenberg/pull/23557)) -- WordCount: Add types. ([22077](https://github.com/WordPress/gutenberg/pull/22077)) +- Remove anonymous components from global styles sidebar. ([26604](https://github.com/WordPress/gutenberg/pull/26604)) +- Add types to components: + - Tip. ([26173](https://github.com/WordPress/gutenberg/pull/26173)) + - BaseControl and VisuallyHidden. ([26078](https://github.com/WordPress/gutenberg/pull/26078)) +- Improve @wordpress/I18n types. ([26171](https://github.com/WordPress/gutenberg/pull/26171)) +- Migrate to builtin data controls. ([25993](https://github.com/WordPress/gutenberg/pull/25993)) ([25949](https://github.com/WordPress/gutenberg/pull/25949)) ([25773](https://github.com/WordPress/gutenberg/pull/25773)) ([25990](https://github.com/WordPress/gutenberg/pull/25990)) ([26509](https://github.com/WordPress/gutenberg/pull/26509)) ([25772](https://github.com/WordPress/gutenberg/pull/25772)) +- Chore: Ensure WordPress packages share the same hoisted dependencies. ([26453](https://github.com/WordPress/gutenberg/pull/26453)) +- Use CSS-in-JS in @wordpress/components: + - Spinner. ([26433](https://github.com/WordPress/gutenberg/pull/26433)) + - Disabled. ([25843](https://github.com/WordPress/gutenberg/pull/25843)) + +### Build Tooling + +- Components: Copy SCSS file from react-dates to components package. ([26534](https://github.com/WordPress/gutenberg/pull/26534)) +- webpack: Replace legacy namedChunks/namedModules options with chunkIds/moduleIds. ([26502](https://github.com/WordPress/gutenberg/pull/26502)) +- Rewrite sideEffects flags to use only positive patterns. ([26452](https://github.com/WordPress/gutenberg/pull/26452)) +- Load the Twenty Twenty-one theme by default in Gutenberg's local environement. ([26414](https://github.com/WordPress/gutenberg/pull/26414)) +- Build: Assign the library exports to window.wp rather than this.wp. ([26272](https://github.com/WordPress/gutenberg/pull/26272)) +- Move to Dart Sass compiler. ([25628](https://github.com/WordPress/gutenberg/pull/25628)) +- Fix composer test failures due to invalid lock. ([26472](https://github.com/WordPress/gutenberg/pull/26472)) +- Update node-watch to 0.7.0. ([26403](https://github.com/WordPress/gutenberg/pull/26403)) +- Release tool: Support multiple RCs. ([25971](https://github.com/WordPress/gutenberg/pull/25971)) +- jest-puppeteer-axe: Migrate to @axe-core/puppeteer. ([25659](https://github.com/WordPress/gutenberg/pull/25659)) +- Improve End-to-End tests stability: + - Fix autosave end-to-end tests. ([26416](https://github.com/WordPress/gutenberg/pull/26416)) + - Fix 'Multi entity saving -> site editor' end-to-end failures. ([26371](https://github.com/WordPress/gutenberg/pull/26371)) + - Fix Twenty Twenty One related end-to-end test failures. ([26341](https://github.com/WordPress/gutenberg/pull/26341)) + - Fix demo test by disabling the welcome dialog. ([26314](https://github.com/WordPress/gutenberg/pull/26314)) + - Disable BlockPreviews from axe-core tests. ([26527](https://github.com/WordPress/gutenberg/pull/26527)) + - Site editor: Fix end-to-end tests navigation panel. ([26454](https://github.com/WordPress/gutenberg/pull/26454)) + - Consolidate sequential multi-entity-saving tests. ([26373](https://github.com/WordPress/gutenberg/pull/26373)) + - Make the adding patterns test stable. ([26345](https://github.com/WordPress/gutenberg/pull/26345)) + + +### Various + +- Create Block: Update the list of categories to pick from. ([26448](https://github.com/WordPress/gutenberg/pull/26448)) +- Removes extra fullstop from preferences. ([26586](https://github.com/WordPress/gutenberg/pull/26586)) +- @wordpress/scripts: Configure all the tools to skip `vendor` folder. ([26450](https://github.com/WordPress/gutenberg/pull/26450)) + + += 9.2.2 = + + +### Bug Fixes + +- Fix widget previews in the widget screen https://github.com/WordPress/gutenberg/pull/26356 https://github.com/WordPress/gutenberg/pull/26417 + + += 9.2.1 = + +### Bug Fixes + + - Code block: preserve indentation on paste + - Fix block supports for inner blocks + - Fix archives block render function + + += 9.2.0 = + +### Features + +- Add video tracks functionality. ([25861](https://github.com/WordPress/gutenberg/pull/25861)) +- Transform multiple selected blocks to Columns block. ([25829](https://github.com/WordPress/gutenberg/pull/25829)) +- Cover: Add repeated background option. ([26001](https://github.com/WordPress/gutenberg/pull/26001)) + +### Enhancements + +- Add dropdown button to view templates in sidebar. ([26132](https://github.com/WordPress/gutenberg/pull/26132)) +- Gallery block: Use image caption as fallback for alt text. ([26082](https://github.com/WordPress/gutenberg/pull/26082)) +- Table block: Use hooks + API v2. ([26065](https://github.com/WordPress/gutenberg/pull/26065)) +- Refactor document actions to handle template part titles. ([26043](https://github.com/WordPress/gutenberg/pull/26043)) +- Info panel layout improvement. ([26017](https://github.com/WordPress/gutenberg/pull/26017)) +- Remove non-core blocks from default editor content. ([25844](https://github.com/WordPress/gutenberg/pull/25844)) +- Add very basic template information dropdown. ([25757](https://github.com/WordPress/gutenberg/pull/25757)) +- Rename "Options" modal to "Preferences". ([25683](https://github.com/WordPress/gutenberg/pull/25683)) +- Add single column functionality to the Columns block. ([24065](https://github.com/WordPress/gutenberg/pull/24065)) +- Add more writing flow options: Reduced UI, theme styles, spotlight. ([22494](https://github.com/WordPress/gutenberg/pull/22494)) +- Add option to make Post Featured Image a link. ([25714](https://github.com/WordPress/gutenberg/pull/25714)) +- Widgets Screen: + - Add legacy widget inspector card component. ([26142](https://github.com/WordPress/gutenberg/pull/26142)) + - Show the legacy widget name in list view. ([26138](https://github.com/WordPress/gutenberg/pull/26138)) + - Add unsaved changes warning to widgets screen. ([26081](https://github.com/WordPress/gutenberg/pull/26081)) + - Display Widget Area's name and description in the sidebar. ([25943](https://github.com/WordPress/gutenberg/pull/25943)) + - Widgets editor: Add basic options for extensibility. ([25758](https://github.com/WordPress/gutenberg/pull/25758)) + - Disallow multiple instances of reference widgets. ([26148](https://github.com/WordPress/gutenberg/pull/26148)) + - Embed widget type. ([26093](https://github.com/WordPress/gutenberg/pull/26093)) + - Add widget type endpoint. ([26042](https://github.com/WordPress/gutenberg/pull/26042)) + - Make edit-widgets package public. ([26016](https://github.com/WordPress/gutenberg/pull/26016)) + - Uncollapse widget area when block is dragged over. ([25992](https://github.com/WordPress/gutenberg/pull/25992)) + - Add meaningful labels for the Widgets screen ARIA landmarks. ([25867](https://github.com/WordPress/gutenberg/pull/25867)) + - Load custom block assets. ([25826](https://github.com/WordPress/gutenberg/pull/25826)) + - Test for storing raw html in widgets. ([24886](https://github.com/WordPress/gutenberg/pull/24886)) + +### New APIs + +- Make block supports server-side explicit. ([26192](https://github.com/WordPress/gutenberg/pull/26192)) +- New hook: UseDebounce for speak function. ([25948](https://github.com/WordPress/gutenberg/pull/25948)) +- Make the custom spacing theme support flag and block support API stable. ([25788](https://github.com/WordPress/gutenberg/pull/25788)) +- Mark the line height support flag as stable. ([25769](https://github.com/WordPress/gutenberg/pull/25769)) +- Mark the font size support flag as stable. ([25695](https://github.com/WordPress/gutenberg/pull/25695)) +- Mark the color support flag as stable. ([25694](https://github.com/WordPress/gutenberg/pull/25694)) +- Add a button to allow resetting the ComboboxControl value. ([25692](https://github.com/WordPress/gutenberg/pull/25692)) +- Block API: Light block edit/save symmetry. ([25644](https://github.com/WordPress/gutenberg/pull/25644)) +- Block API: Stabilize light block hook. ([25642](https://github.com/WordPress/gutenberg/pull/25642)) +- Inner blocks: Try hook approach. ([25633](https://github.com/WordPress/gutenberg/pull/25633)) +- API: Stabilize localAutosave() as autosave( { local: True } ). ([20149](https://github.com/WordPress/gutenberg/pull/20149)) + +### Experiments + +- Query Block: + - Add Custom Post Types support in Query block. ([25903](https://github.com/WordPress/gutenberg/pull/25903)) + - Set focus on Query block on insertion. ([26267](https://github.com/WordPress/gutenberg/pull/26267)) + - Add loading message to Query block while fetching results. ([26199](https://github.com/WordPress/gutenberg/pull/26199)) + - Add no results placeholder in Query block. ([25984](https://github.com/WordPress/gutenberg/pull/25984)) +- Site Editor: + - Clear the active menu state when closing the sidebar. ([25957](https://github.com/WordPress/gutenberg/pull/25957)) + - Add missing localization to the templates sidebar. ([25897](https://github.com/WordPress/gutenberg/pull/25897)) + - Mount both wp_template and wp_template_part EntityProviders to avoid remounting. ([25870](https://github.com/WordPress/gutenberg/pull/25870)) + - Navigation templates. ([25739](https://github.com/WordPress/gutenberg/pull/25739)) + - Update Navigation Panel Toggle UI. ([25622](https://github.com/WordPress/gutenberg/pull/25622)) + - Move page switcher to navigation panel. ([25620](https://github.com/WordPress/gutenberg/pull/25620)) + - Add template switcher to navigation panel. ([25615](https://github.com/WordPress/gutenberg/pull/25615)) + - Pass editor features dynamically. ([25795](https://github.com/WordPress/gutenberg/pull/25795)) + +### Bug Fixes + +- Fix for current_parsed_blocks value when block has inner blocks. ([26291](https://github.com/WordPress/gutenberg/pull/26291)) +- Fix updating clientId mapping. ([26290](https://github.com/WordPress/gutenberg/pull/26290)) +- Fix typo in wrapper attributes. ([26282](https://github.com/WordPress/gutenberg/pull/26282)) +- Fix: Keep the 'Insert from URL' entered value on ImagePlaceholder. ([26262](https://github.com/WordPress/gutenberg/pull/26262)) +- Fix align order in heading block. ([26260](https://github.com/WordPress/gutenberg/pull/26260)) +- Widgets screen: Add a filter function to `registerCoreBlock`. ([26259](https://github.com/WordPress/gutenberg/pull/26259)) +- Use ToolbarButtons instead of Buttons in the Legacy Widget block's toolbar. ([26258](https://github.com/WordPress/gutenberg/pull/26258)) +- Widgets screen: Add "Browse all" option to the inserter. ([26256](https://github.com/WordPress/gutenberg/pull/26256)) +- Fix: Post schedule label showing wrong time if site and user timezones did not match. ([26212](https://github.com/WordPress/gutenberg/pull/26212)) +- Fix Site Title block's heading levels appearance. ([26202](https://github.com/WordPress/gutenberg/pull/26202)) +- Writing flow: Fix in-between inserter for aligned blocks. ([26197](https://github.com/WordPress/gutenberg/pull/26197)) +- Fix Site Tagline block's text alignment. ([26191](https://github.com/WordPress/gutenberg/pull/26191)) +- Fix separator and spacer blocks after api v2 refactoring. ([26157](https://github.com/WordPress/gutenberg/pull/26157)) +- Global Styles sidebar (blocks tab): Protect against not registered blocks. ([26149](https://github.com/WordPress/gutenberg/pull/26149)) +- Block templates: Recognize and convert old or derivative block types to their canonical form. ([26147](https://github.com/WordPress/gutenberg/pull/26147)) +- Fix editing Legacy Widgets doesn't enable "Save" button. ([26144](https://github.com/WordPress/gutenberg/pull/26144)) +- Fix Cover width regression. ([26143](https://github.com/WordPress/gutenberg/pull/26143)) +- Fix tabbing in widgets not triggering auto-scrolling. ([26139](https://github.com/WordPress/gutenberg/pull/26139)) +- Cover block: Improve overlay opacity handling. ([26133](https://github.com/WordPress/gutenberg/pull/26133)) +- Fix icons type annotation. ([26129](https://github.com/WordPress/gutenberg/pull/26129)) +- FullscreenMode: Remove the is-fullscreen-mode CSS class from body on unmount. ([26103](https://github.com/WordPress/gutenberg/pull/26103)) +- Make sure Global Styles CPT includes a theme reference. ([26061](https://github.com/WordPress/gutenberg/pull/26061)) +- Restrict legacy widget block to only being a child of widget area. ([26053](https://github.com/WordPress/gutenberg/pull/26053)) +- Fix/wrong classes search block. ([26052](https://github.com/WordPress/gutenberg/pull/26052)) +- Fix drag and drop in empty widget area. ([26051](https://github.com/WordPress/gutenberg/pull/26051)) +- Fix unit tests by updating cover block fixtures. ([26044](https://github.com/WordPress/gutenberg/pull/26044)) +- Upgrade autoprefixer to fix fit-content in firefox. ([26019](https://github.com/WordPress/gutenberg/pull/26019)) +- Fix widget area title font. ([26018](https://github.com/WordPress/gutenberg/pull/26018)) +- Widgets screen: Fix WP Admin Bar Widgets screen link. ([26015](https://github.com/WordPress/gutenberg/pull/26015)) +- Hide parent selector in widget area. ([26011](https://github.com/WordPress/gutenberg/pull/26011)) +- Document Actions: Fix unexpected label wrapping. ([26004](https://github.com/WordPress/gutenberg/pull/26004)) +- Fix template part theme identifier. ([25995](https://github.com/WordPress/gutenberg/pull/25995)) +- Show all widget areas on widget screen. ([25977](https://github.com/WordPress/gutenberg/pull/25977)) +- Fix block editor example in storybook. ([25976](https://github.com/WordPress/gutenberg/pull/25976)) +- Fix 9:16 aspect ratio styling. ([25972](https://github.com/WordPress/gutenberg/pull/25972)) +- Fix gallery caption not centered in the front-end issue. ([25962](https://github.com/WordPress/gutenberg/pull/25962)) +- Widgets screen: Add save keyboard shortcut. ([25944](https://github.com/WordPress/gutenberg/pull/25944)) +- Widgets screen: Remove default hover background in panel title. ([25939](https://github.com/WordPress/gutenberg/pull/25939)) +- Fix failing previews end-to-end test. ([25938](https://github.com/WordPress/gutenberg/pull/25938)) +- Fix input control drag and box control change. ([25933](https://github.com/WordPress/gutenberg/pull/25933)) +- Fix end-to-end tests related to template parts. ([25923](https://github.com/WordPress/gutenberg/pull/25923)) +- Fix insertion indicator margin. ([25893](https://github.com/WordPress/gutenberg/pull/25893)) +- Fix blue line indicator not showing at the end. ([25849](https://github.com/WordPress/gutenberg/pull/25849)) +- Fix incorrect attribute type specified in Search block. ([25813](https://github.com/WordPress/gutenberg/pull/25813)) +- Document Actions: Fix Block Editor Inserter Overlap with Document Titles. ([25801](https://github.com/WordPress/gutenberg/pull/25801)) +- Fix PHP warning in widget utils REST controller. ([25797](https://github.com/WordPress/gutenberg/pull/25797)) +- Include edit-widgets php files in build. ([25792](https://github.com/WordPress/gutenberg/pull/25792)) +- Docs: Fix typo in Git Workflow. ([25779](https://github.com/WordPress/gutenberg/pull/25779)) +- Widgets screen: Fix widget-area accessibility. ([25732](https://github.com/WordPress/gutenberg/pull/25732)) +- Widgets screen: Fix insertion point in widget areas. ([25727](https://github.com/WordPress/gutenberg/pull/25727)) +- Document Settings: Fix document title hover and select animations. ([25719](https://github.com/WordPress/gutenberg/pull/25719)) +- Define text color for warning message component. ([25713](https://github.com/WordPress/gutenberg/pull/25713)) +- RichText: Remove native props for web. ([25700](https://github.com/WordPress/gutenberg/pull/25700)) +- Use h3 in the legacy widget title. ([25690](https://github.com/WordPress/gutenberg/pull/25690)) +- Navigation block: Use unbounded query when requesting top level pages. ([25689](https://github.com/WordPress/gutenberg/pull/25689)) +- Document Actions: Fix document title misalignment with an open nav sidebar. ([25630](https://github.com/WordPress/gutenberg/pull/25630)) +- Fix React error in @wordpress/block-editor documentation usage example caused by applying args to setState call. ([25492](https://github.com/WordPress/gutenberg/pull/25492)) +- Cover Block: Show spinner while uploading. ([25401](https://github.com/WordPress/gutenberg/pull/25401)) +- Button block: Reduce chance of style conflicts. ([24919](https://github.com/WordPress/gutenberg/pull/24919)) +- Fix skipped shortcode transforms in raw handling. ([22840](https://github.com/WordPress/gutenberg/pull/22840)) +- Media links: Fix linking for images inserted from URL. ([22195](https://github.com/WordPress/gutenberg/pull/22195)) +- Refactor reusable block edit component using hooks (and fix interactions with multiple instances of the same reusable block). ([21427](https://github.com/WordPress/gutenberg/pull/21427)) + +### Performance + +- Paragraph: Avoid selector to improve performance. ([26150](https://github.com/WordPress/gutenberg/pull/26150)) +- Remove transition on block selection indicator. ([25974](https://github.com/WordPress/gutenberg/pull/25974)) +- Widgets screen: Preload request to /sidebars. ([25726](https://github.com/WordPress/gutenberg/pull/25726)) + +### Documentation + +- Bring the block-based theme tutorial up to date. ([25830](https://github.com/WordPress/gutenberg/pull/25830)) +- Add more CI status badges to README. ([26090](https://github.com/WordPress/gutenberg/pull/26090)) +- Getting started: MAMP: Add tip to fix WP-CLI. ([26057](https://github.com/WordPress/gutenberg/pull/26057)) +- Update colors readme with additional definitions. ([25954](https://github.com/WordPress/gutenberg/pull/25954)) +- Document isMultiBlock param for block transforms. ([25952](https://github.com/WordPress/gutenberg/pull/25952)) +- Update CI status badge in README. ([25907](https://github.com/WordPress/gutenberg/pull/25907)) +- Adds missing Curly brace. ([25748](https://github.com/WordPress/gutenberg/pull/25748)) +- Add documentation for colors component. ([25567](https://github.com/WordPress/gutenberg/pull/25567)) +- InspectorAdvancedControls: Add README.md. ([25566](https://github.com/WordPress/gutenberg/pull/25566)) +- Add documentation for useResizeCanvas. ([25558](https://github.com/WordPress/gutenberg/pull/25558)) +- Add/block navigation component readme. ([24882](https://github.com/WordPress/gutenberg/pull/24882)) +- Update Block Based Themes Documentation. ([25710](https://github.com/WordPress/gutenberg/pull/25710)) + +### Code Quality + +- Pass all extra attributes down in get_block_wrapper_attributes. ([26280](https://github.com/WordPress/gutenberg/pull/26280)) +- Editor: Refactor PostFormatPanel to use React hooks. ([26273](https://github.com/WordPress/gutenberg/pull/26273)) +- BlockListBlock: Reduce passed props. ([26251](https://github.com/WordPress/gutenberg/pull/26251)) +- Editor: Refactor PostFormat to use React hooks. ([26238](https://github.com/WordPress/gutenberg/pull/26238)) +- Latest posts: Use hooks + API v2. ([26122](https://github.com/WordPress/gutenberg/pull/26122)) +- Latest comments: API v2. ([26113](https://github.com/WordPress/gutenberg/pull/26113)) +- Categories block: Use API v2. ([26112](https://github.com/WordPress/gutenberg/pull/26112)) +- Rename ReusableBlocksButtons to ReusableBlocksMenuItems. ([26099](https://github.com/WordPress/gutenberg/pull/26099)) +- Reusable block: Use API v2. ([26091](https://github.com/WordPress/gutenberg/pull/26091)) +- Gallery block: Use hooks. ([26088](https://github.com/WordPress/gutenberg/pull/26088)) +- Pullquote block: Use hooks + API v2. ([26068](https://github.com/WordPress/gutenberg/pull/26068)) +- Components: Start adding types progressively. ([26066](https://github.com/WordPress/gutenberg/pull/26066)) +- File block: Use hooks + API v2. ([26063](https://github.com/WordPress/gutenberg/pull/26063)) +- HTML block: Use hooks and API v2. ([26055](https://github.com/WordPress/gutenberg/pull/26055)) +- Update all blocks to API v2. ([26054](https://github.com/WordPress/gutenberg/pull/26054)) +- editor: Remove two unused registry controls. ([26048](https://github.com/WordPress/gutenberg/pull/26048)) +- Tweak styles of the document actions area. ([26038](https://github.com/WordPress/gutenberg/pull/26038)) +- Site Editor: Navigation panel replace hardcoded menu strings with constants. ([26026](https://github.com/WordPress/gutenberg/pull/26026)) +- Move left sidebar state to redux. ([26003](https://github.com/WordPress/gutenberg/pull/26003)) +- Refactor Categories to function component. ([25806](https://github.com/WordPress/gutenberg/pull/25806)) +- Classic block: Use hooks. ([25737](https://github.com/WordPress/gutenberg/pull/25737)) +- Remove animation from mover buttons. ([25728](https://github.com/WordPress/gutenberg/pull/25728)) +- Move widget-area to edit-widgets. ([25673](https://github.com/WordPress/gutenberg/pull/25673)) +- InnerBlocks: Add select dependencies. ([25672](https://github.com/WordPress/gutenberg/pull/25672)) +- Refactor Buttons block native edit component to use hooks. ([25636](https://github.com/WordPress/gutenberg/pull/25636)) +- Data: Build the basic data controls into every store. ([25362](https://github.com/WordPress/gutenberg/pull/25362)) +- Block Editor: Use optional chaining and nullish coalescing instead of Lodash.get. ([23632](https://github.com/WordPress/gutenberg/pull/23632)) +- Refactor Latest Comments block to use function component. ([23557](https://github.com/WordPress/gutenberg/pull/23557)) +- WordCount: Add types. ([22077](https://github.com/WordPress/gutenberg/pull/22077)) ### Security -- PostCSS Plugins Preset: Update vulnerable dependency. ([26140](https://github.com/WordPress/gutenberg/pull/26140)) +- PostCSS Plugins Preset: Update vulnerable dependency. ([26140](https://github.com/WordPress/gutenberg/pull/26140)) ### Breaking Change -- Add separate widgets endpoint. ([25958](https://github.com/WordPress/gutenberg/pull/25958)) +- Add separate widgets endpoint. ([25958](https://github.com/WordPress/gutenberg/pull/25958)) ### Various -- Stabilize batching endpoint as v1. ([26295](https://github.com/WordPress/gutenberg/pull/26295)) -- Make batch opt-in more expressive. ([26292](https://github.com/WordPress/gutenberg/pull/26292)) -- Remove __experimental sidebars endpoint shim. ([26288](https://github.com/WordPress/gutenberg/pull/26288)) -- Warn about using core/batch-processing store. ([26287](https://github.com/WordPress/gutenberg/pull/26287)) -- Remove WP_REST_Widget_Utils_Controller class. ([26274](https://github.com/WordPress/gutenberg/pull/26274)) -- Minor iterations to grouping for preferences panel. ([26198](https://github.com/WordPress/gutenberg/pull/26198)) -- Allow tranform to Columns from a single block. ([26185](https://github.com/WordPress/gutenberg/pull/26185)) -- Use batch processing in edit-widgets package. ([26164](https://github.com/WordPress/gutenberg/pull/26164)) -- Minor updates to @wordpress/edit-widgets for easier Core integration. ([26136](https://github.com/WordPress/gutenberg/pull/26136)) -- TextareaControl: Use CSS-in-JS. ([26131](https://github.com/WordPress/gutenberg/pull/26131)) -- Add template lock attribute to column and group. ([26128](https://github.com/WordPress/gutenberg/pull/26128)) -- Reusable blocks support for widgets editor. ([26097](https://github.com/WordPress/gutenberg/pull/26097)) -- Bump @actions/core from 1.0.0 to 1.2.6. ([26087](https://github.com/WordPress/gutenberg/pull/26087)) -- First pass at using the new sidebars and widget endpoints. ([26086](https://github.com/WordPress/gutenberg/pull/26086)) -- Don't rely on the exact count of registered widgets. ([26085](https://github.com/WordPress/gutenberg/pull/26085)) -- Try: Make class and style tests less brittle. ([26079](https://github.com/WordPress/gutenberg/pull/26079)) -- Components: Remove size prop from Dashicon. ([26067](https://github.com/WordPress/gutenberg/pull/26067)) -- Adjust media-text attributes to default stacked on mobile to true. ([26041](https://github.com/WordPress/gutenberg/pull/26041)) -- Support batch requests in data layer. ([26024](https://github.com/WordPress/gutenberg/pull/26024)) -- Fallback for dropcap when __experimentalFeatures is not present. ([25979](https://github.com/WordPress/gutenberg/pull/25979)) -- Site Editor: Clear the active menu state when closing the sidebar. ([25957](https://github.com/WordPress/gutenberg/pull/25957)) -- Social Links: Update Placeholder experience when first inserting Social Links. ([25941](https://github.com/WordPress/gutenberg/pull/25941)) -- Check that get_current_screen is callable. ([25935](https://github.com/WordPress/gutenberg/pull/25935)) -- Social Link: Rename mail to email. ([25924](https://github.com/WordPress/gutenberg/pull/25924)) -- Autocomplete: Use hooks. ([25922](https://github.com/WordPress/gutenberg/pull/25922)) -- Skip broken template-part end-to-end test until it can be fixed. ([25918](https://github.com/WordPress/gutenberg/pull/25918)) -- Heading block: Add wide and full width options. ([25917](https://github.com/WordPress/gutenberg/pull/25917)) -- Social Links: Avoid conflict with themes ul text-indent. ([25916](https://github.com/WordPress/gutenberg/pull/25916)) -- Site editor: Store navigation panel's active menu state in the store. ([25906](https://github.com/WordPress/gutenberg/pull/25906)) -- Version bump to 9.1.1. ([25904](https://github.com/WordPress/gutenberg/pull/25904)) -- Export and document LinkControl's building blocks. ([25901](https://github.com/WordPress/gutenberg/pull/25901)) -- Prevent network requests related to ephemeral posts in the widgets editor. ([25899](https://github.com/WordPress/gutenberg/pull/25899)) -- Site Editor: Add missing localization to the templates sidebar. ([25897](https://github.com/WordPress/gutenberg/pull/25897)) -- FSE Navigation Sidebar: Move navigation sidebar in DOM hierarchy. ([25884](https://github.com/WordPress/gutenberg/pull/25884)) -- Template part selection component - fix keyboard controls. ([25881](https://github.com/WordPress/gutenberg/pull/25881)) -- FSE Document actions - wrap with heading. ([25874](https://github.com/WordPress/gutenberg/pull/25874)) -- Site editor: Mount both wp_template and wp_template_part EntityProviders to avoid remounting. ([25870](https://github.com/WordPress/gutenberg/pull/25870)) -- Extract @wordpress/reusable-blocks from @wordpress/editor. ([25859](https://github.com/WordPress/gutenberg/pull/25859)) -- Unify help description text styling. ([25852](https://github.com/WordPress/gutenberg/pull/25852)) -- BaseControl: Use CSS-in-JS. ([25842](https://github.com/WordPress/gutenberg/pull/25842)) -- Iterations on options modal. ([25837](https://github.com/WordPress/gutenberg/pull/25837)) -- BlockSelectionClearer: Use hooks. ([25824](https://github.com/WordPress/gutenberg/pull/25824)) -- Update pull request documentation URLs. ([25815](https://github.com/WordPress/gutenberg/pull/25815)) -- Add a dark mode to the post title. ([25796](https://github.com/WordPress/gutenberg/pull/25796)) -- Automatically generate required preset classes. ([25768](https://github.com/WordPress/gutenberg/pull/25768)) -- Ensure focus of input when InputControl spinner arrows are pressed. ([25753](https://github.com/WordPress/gutenberg/pull/25753)) -- External Link: Use CSS-in-JS. ([25751](https://github.com/WordPress/gutenberg/pull/25751)) -- Site Editor: Navigation templates. ([25739](https://github.com/WordPress/gutenberg/pull/25739)) -- Update improve backward compatibility for deprecated settings. ([25738](https://github.com/WordPress/gutenberg/pull/25738)) -- Initialize the state before rendering widgets editor. ([25736](https://github.com/WordPress/gutenberg/pull/25736)) -- Add color palette edit functionality to global styles. ([25711](https://github.com/WordPress/gutenberg/pull/25711)) -- UnitControl: Enable keyboard access (via tab) to unit select by default. ([25704](https://github.com/WordPress/gutenberg/pull/25704)) -- Add EditorStyles CSS to the widgets editor. ([25699](https://github.com/WordPress/gutenberg/pull/25699)) -- Display before_widget/after_widget when rendering WP_Widget_Block. ([25693](https://github.com/WordPress/gutenberg/pull/25693)) -- Remove the right margin for the right-most list items in the lastest posts block. ([25688](https://github.com/WordPress/gutenberg/pull/25688)) -- Update and move some Query filters. ([25674](https://github.com/WordPress/gutenberg/pull/25674)) -- Remove duplicate key from tsconfig.base.json. ([25664](https://github.com/WordPress/gutenberg/pull/25664)) -- Try adding a 'spotlight mode' type effect when template part or child is selected. ([25656](https://github.com/WordPress/gutenberg/pull/25656)) -- Site Editor: Update Navigation Panel Toggle UI. ([25622](https://github.com/WordPress/gutenberg/pull/25622)) -- Site Editor: Move page switcher to navigation panel. ([25620](https://github.com/WordPress/gutenberg/pull/25620)) -- Site Editor: Add template switcher to navigation panel. ([25615](https://github.com/WordPress/gutenberg/pull/25615)) -- Gallery: Add labels to img, figure and figcaption elements for accessibility. ([25560](https://github.com/WordPress/gutenberg/pull/25560)) -- Navigation component: Add back button click handler. ([25556](https://github.com/WordPress/gutenberg/pull/25556)) -- Hide the quick side inserter when the user is typing. ([25548](https://github.com/WordPress/gutenberg/pull/25548)) -- Add border to block "Edit as HTML" style. ([25539](https://github.com/WordPress/gutenberg/pull/25539)) -- Show PostFeaturedImage in editor. ([25412](https://github.com/WordPress/gutenberg/pull/25412)) -- Don't allow duplicate selectors in styles. ([25399](https://github.com/WordPress/gutenberg/pull/25399)) -- Gallery: Add a margin declaration. ([25291](https://github.com/WordPress/gutenberg/pull/25291)) -- Page parent selector with ComboboxControl. ([25267](https://github.com/WordPress/gutenberg/pull/25267)) -- Add Align support to Separator block. ([25147](https://github.com/WordPress/gutenberg/pull/25147)) -- REST API: Introduce batch controller. ([25096](https://github.com/WordPress/gutenberg/pull/25096)) -- Upgrade TypeScript to v4. ([24892](https://github.com/WordPress/gutenberg/pull/24892)) -- Use `UnitControl` instead of `RangeControl` for column width. ([24711](https://github.com/WordPress/gutenberg/pull/24711)) -- Add UI tests to unsupported block editor. ([23729](https://github.com/WordPress/gutenberg/pull/23729)) -- Add a description to the Site Title block. ([23462](https://github.com/WordPress/gutenberg/pull/23462)) -- Add storybook story for the FocusableIframe component. ([22324](https://github.com/WordPress/gutenberg/pull/22324)) +- Stabilize batching endpoint as v1. ([26295](https://github.com/WordPress/gutenberg/pull/26295)) +- Make batch opt-in more expressive. ([26292](https://github.com/WordPress/gutenberg/pull/26292)) +- Remove experimental sidebars endpoint shim. ([26288](https://github.com/WordPress/gutenberg/pull/26288)) +- Warn about using core/batch-processing store. ([26287](https://github.com/WordPress/gutenberg/pull/26287)) +- Remove WP_REST_Widget_Utils_Controller class. ([26274](https://github.com/WordPress/gutenberg/pull/26274)) +- Minor iterations to grouping for preferences panel. ([26198](https://github.com/WordPress/gutenberg/pull/26198)) +- Allow transform to Columns from a single block. ([26185](https://github.com/WordPress/gutenberg/pull/26185)) +- Use batch processing in edit-widgets package. ([26164](https://github.com/WordPress/gutenberg/pull/26164)) +- Minor updates to @wordpress/edit-widgets for easier Core integration. ([26136](https://github.com/WordPress/gutenberg/pull/26136)) +- TextareaControl: Use CSS-in-JS. ([26131](https://github.com/WordPress/gutenberg/pull/26131)) +- Add template lock attribute to column and group. ([26128](https://github.com/WordPress/gutenberg/pull/26128)) +- Reusable blocks support for widgets editor. ([26097](https://github.com/WordPress/gutenberg/pull/26097)) +- Bump @actions/core from 1.0.0 to 1.2.6. ([26087](https://github.com/WordPress/gutenberg/pull/26087)) +- First pass at using the new sidebars and widget endpoints. ([26086](https://github.com/WordPress/gutenberg/pull/26086)) +- Don't rely on the exact count of registered widgets. ([26085](https://github.com/WordPress/gutenberg/pull/26085)) +- Try: Make class and style tests less brittle. ([26079](https://github.com/WordPress/gutenberg/pull/26079)) +- Components: Remove size prop from Dashicon. ([26067](https://github.com/WordPress/gutenberg/pull/26067)) +- Adjust media-text attributes to default stacked on mobile to true. ([26041](https://github.com/WordPress/gutenberg/pull/26041)) +- Support batch requests in data layer. ([26024](https://github.com/WordPress/gutenberg/pull/26024)) +- Fallback for dropcap when experimentalFeatures is not present. ([25979](https://github.com/WordPress/gutenberg/pull/25979)) +- Social Links: Update Placeholder experience when first inserting Social Links. ([25941](https://github.com/WordPress/gutenberg/pull/25941)) +- Check that get_current_screen is callable. ([25935](https://github.com/WordPress/gutenberg/pull/25935)) +- Social Link: Rename mail to email. ([25924](https://github.com/WordPress/gutenberg/pull/25924)) +- Autocomplete: Use hooks. ([25922](https://github.com/WordPress/gutenberg/pull/25922)) +- Skip broken template-part end-to-end test until it can be fixed. ([25918](https://github.com/WordPress/gutenberg/pull/25918)) +- Heading block: Add wide and full width options. ([25917](https://github.com/WordPress/gutenberg/pull/25917)) +- Social Links: Avoid conflict with themes ul text-indent. ([25916](https://github.com/WordPress/gutenberg/pull/25916)) +- Site editor: Store navigation panel's active menu state in the store. ([25906](https://github.com/WordPress/gutenberg/pull/25906)) +- Version bump to 9.1.1. ([25904](https://github.com/WordPress/gutenberg/pull/25904)) +- Export and document LinkControl's building blocks. ([25901](https://github.com/WordPress/gutenberg/pull/25901)) +- Prevent network requests related to ephemeral posts in the widgets editor. ([25899](https://github.com/WordPress/gutenberg/pull/25899)) +- FSE Navigation Sidebar: Move navigation sidebar in DOM hierarchy. ([25884](https://github.com/WordPress/gutenberg/pull/25884)) +- Template part selection component - fix keyboard controls. ([25881](https://github.com/WordPress/gutenberg/pull/25881)) +- FSE Document actions - wrap with heading. ([25874](https://github.com/WordPress/gutenberg/pull/25874)) +- Extract @wordpress/reusable-blocks from @wordpress/editor. ([25859](https://github.com/WordPress/gutenberg/pull/25859)) +- Unify help description text styling. ([25852](https://github.com/WordPress/gutenberg/pull/25852)) +- BaseControl: Use CSS-in-JS. ([25842](https://github.com/WordPress/gutenberg/pull/25842)) +- Iterations on options modal. ([25837](https://github.com/WordPress/gutenberg/pull/25837)) +- BlockSelectionClearer: Use hooks. ([25824](https://github.com/WordPress/gutenberg/pull/25824)) +- Update pull request documentation URLs. ([25815](https://github.com/WordPress/gutenberg/pull/25815)) +- Add a dark mode to the post title. ([25796](https://github.com/WordPress/gutenberg/pull/25796)) +- Automatically generate required preset classes. ([25768](https://github.com/WordPress/gutenberg/pull/25768)) +- Ensure focus of input when InputControl spinner arrows are pressed. ([25753](https://github.com/WordPress/gutenberg/pull/25753)) +- External Link: Use CSS-in-JS. ([25751](https://github.com/WordPress/gutenberg/pull/25751)) +- Update improve backward compatibility for deprecated settings. ([25738](https://github.com/WordPress/gutenberg/pull/25738)) +- Initialize the state before rendering widgets editor. ([25736](https://github.com/WordPress/gutenberg/pull/25736)) +- Add color palette edit functionality to global styles. ([25711](https://github.com/WordPress/gutenberg/pull/25711)) +- UnitControl: Enable keyboard access (via tab) to unit select by default. ([25704](https://github.com/WordPress/gutenberg/pull/25704)) +- Add EditorStyles CSS to the widgets editor. ([25699](https://github.com/WordPress/gutenberg/pull/25699)) +- Display before_widget/after_widget when rendering WP_Widget_Block. ([25693](https://github.com/WordPress/gutenberg/pull/25693)) +- Remove the right margin for the right-most list items in the lastest posts block. ([25688](https://github.com/WordPress/gutenberg/pull/25688)) +- Update and move some Query filters. ([25674](https://github.com/WordPress/gutenberg/pull/25674)) +- Remove duplicate key from tsconfig.base.json. ([25664](https://github.com/WordPress/gutenberg/pull/25664)) +- Try adding a 'spotlight mode' type effect when template part or child is selected. ([25656](https://github.com/WordPress/gutenberg/pull/25656)) +- Gallery: Add labels to img, figure and figcaption elements for accessibility. ([25560](https://github.com/WordPress/gutenberg/pull/25560)) +- Navigation component: Add back button click handler. ([25556](https://github.com/WordPress/gutenberg/pull/25556)) +- Hide the quick side inserter when the user is typing. ([25548](https://github.com/WordPress/gutenberg/pull/25548)) +- Add border to block "Edit as HTML" style. ([25539](https://github.com/WordPress/gutenberg/pull/25539)) +- Show PostFeaturedImage in editor. ([25412](https://github.com/WordPress/gutenberg/pull/25412)) +- Don't allow duplicate selectors in styles. ([25399](https://github.com/WordPress/gutenberg/pull/25399)) +- Gallery: Add a margin declaration. ([25291](https://github.com/WordPress/gutenberg/pull/25291)) +- Page parent selector with ComboboxControl. ([25267](https://github.com/WordPress/gutenberg/pull/25267)) +- Add Align support to Separator block. ([25147](https://github.com/WordPress/gutenberg/pull/25147)) +- REST API: Introduce batch controller. ([25096](https://github.com/WordPress/gutenberg/pull/25096)) +- Upgrade TypeScript to v4. ([24892](https://github.com/WordPress/gutenberg/pull/24892)) +- Use `UnitControl` instead of `RangeControl` for column width. ([24711](https://github.com/WordPress/gutenberg/pull/24711)) +- Add UI tests to unsupported block editor. ([23729](https://github.com/WordPress/gutenberg/pull/23729)) +- Add a description to the Site Title block. ([23462](https://github.com/WordPress/gutenberg/pull/23462)) +- Add storybook story for the FocusableIframe component. ([22324](https://github.com/WordPress/gutenberg/pull/22324)) = 9.1.0 = @@ -281,7 +799,7 @@ - Improve the Audio block shortcode transform to account for all sources. ([25114](https://github.com/WordPress/gutenberg/pull/25114)) - Code block: Allow HTML editing & rich text content. ([24689](https://github.com/WordPress/gutenberg/pull/24689)) - Remove appender from unselected Buttons and Social Icons block. ([25518](https://github.com/WordPress/gutenberg/pull/25518)) -- Widgets Screen: +- Widgets Screen: - Register legacy widgets as block variations. ([24905](https://github.com/WordPress/gutenberg/pull/24905)) - Use the default block list appender for the widget areas. ([25635](https://github.com/WordPress/gutenberg/pull/25635)) - Add titles to Legacy Widgets. ([25638](https://github.com/WordPress/gutenberg/pull/25638)) @@ -308,14 +826,14 @@ ### Bug Fixes -- Widgets Screen: +- Widgets Screen: - Auto expand the last selected widget area when opening the inserter. ([25669](https://github.com/WordPress/gutenberg/pull/25669)) - Ensure all widgets are properly initialized when they're added, do not unmount widgets once they're mounted. ([25645](https://github.com/WordPress/gutenberg/pull/25645)) - Fix Legacy widget block previews and use iFrames. ([25443](https://github.com/WordPress/gutenberg/pull/25443)) ([14643](https://github.com/WordPress/gutenberg/pull/14643)) - Report save errors. ([25408](https://github.com/WordPress/gutenberg/pull/25408)) - Fix global inserter. ([24908](https://github.com/WordPress/gutenberg/pull/24908)) - Fix RangeControl direct entry in input field. ([25609](https://github.com/WordPress/gutenberg/pull/25609)) -- A11y: +- A11y: - Fix the color contrast in the code editor. ([25593](https://github.com/WordPress/gutenberg/pull/25593)) - Fix Publish sidebar Cancel button not usable through screen readers. ([25441](https://github.com/WordPress/gutenberg/pull/25441)) - Fix keyboard navigation on the Image block toolbar. ([25127](https://github.com/WordPress/gutenberg/pull/25127)) @@ -326,7 +844,7 @@ - Remove Embed block aspect ratio classes on url change. ([25295](https://github.com/WordPress/gutenberg/pull/25295)) - Remove duplicate help item. ([25283](https://github.com/WordPress/gutenberg/pull/25283)) - Fix Block Directory author average rating formating. ([24732](https://github.com/WordPress/gutenberg/pull/24732)) -- @wordpress/api-fetch: +- @wordpress/api-fetch: - Fix preloading middleware referencing stale data. ([25550](https://github.com/WordPress/gutenberg/pull/25550)) - Check nonce header value before skipping adding it. ([25458](https://github.com/WordPress/gutenberg/pull/25458)) - Use esc_html instead of esc_attr in the Archives block. ([25476](https://github.com/WordPress/gutenberg/pull/25476)) @@ -377,7 +895,7 @@ - Add new block supports page to the handbook. ([25647](https://github.com/WordPress/gutenberg/pull/25647)) - Block Directory: Add developer documentation. ([25591](https://github.com/WordPress/gutenberg/pull/25591)) - Move custom-fields note to the 'Register Meta Field' documentation. ([25584](https://github.com/WordPress/gutenberg/pull/25584)) -- Add Block Editor Components documentation: +- Add Block Editor Components documentation: - Warning ([25574](https://github.com/WordPress/gutenberg/pull/25574)) - FontSizePicker ([25568](https://github.com/WordPress/gutenberg/pull/25568)) - UnitControl ([25565](https://github.com/WordPress/gutenberg/pull/25565)) diff --git a/composer.json b/composer.json index 49d6605108cfef..2a16d1accff3e9 100644 --- a/composer.json +++ b/composer.json @@ -10,19 +10,26 @@ "support": { "issues": "https://github.com/WordPress/gutenberg/issues" }, + "config": { + "process-timeout": 0 + }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "^0.7", "squizlabs/php_codesniffer": "^3.5", "phpcompatibility/php-compatibility": "^9.3", "wp-coding-standards/wpcs": "^2.2", "sirbrillig/phpcs-variable-analysis": "^2.8", - "wp-phpunit/wp-phpunit": "^5.4" + "wp-phpunit/wp-phpunit": "^5.4", + "phpunit/phpunit": "^7.5.20", + "spatie/phpunit-watcher": "^1.23" }, "require": { "composer/installers": "~1.0" }, "scripts": { "format": "phpcbf --standard=phpcs.xml.dist --report-summary --report-source", - "lint": "phpcs --standard=phpcs.xml.dist --runtime-set ignore_warnings_on_exit 1" + "lint": "phpcs --standard=phpcs.xml.dist --runtime-set ignore_warnings_on_exit 1", + "test": "phpunit", + "test:watch": "phpunit-watcher watch < /dev/tty" } } diff --git a/composer.lock b/composer.lock index ca3dde5106f29d..bff5a02fc188c0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "693be7bb8e616544f4b253f4a2168e04", + "content-hash": "a0cee819dff559c9839fd0e98cb4186a", "packages": [ { "name": "composer/installers", @@ -145,37 +145,2824 @@ } ], "packages-dev": [ + { + "name": "clue/stdio-react", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-stdio.git", + "reference": "5f42a3a5a29f52432f0f68b57890353e8ca65069" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-stdio/zipball/5f42a3a5a29f52432f0f68b57890353e8ca65069", + "reference": "5f42a3a5a29f52432f0f68b57890353e8ca65069", + "shasum": "" + }, + "require": { + "clue/term-react": "^1.0 || ^0.1.1", + "clue/utf8-react": "^1.0 || ^0.1", + "php": ">=5.3", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3", + "react/stream": "^1.0 || ^0.7 || ^0.6" + }, + "require-dev": { + "clue/arguments": "^2.0", + "clue/commander": "^1.2", + "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35" + }, + "suggest": { + "ext-mbstring": "Using ext-mbstring should provide slightly better performance for handling I/O" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\Stdio\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@lueck.tv" + } + ], + "description": "Async, event-driven console input & output (STDIN, STDOUT) for truly interactive CLI applications, built on top of ReactPHP", + "homepage": "https://github.com/clue/reactphp-stdio", + "keywords": [ + "async", + "autocomplete", + "autocompletion", + "cli", + "history", + "interactive", + "reactphp", + "readline", + "stdin", + "stdio", + "stdout" + ], + "time": "2019-08-28T12:01:30+00:00" + }, + { + "name": "clue/term-react", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-term.git", + "reference": "eb6eb063eda04a714ef89f066586a2c49588f7ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-term/zipball/eb6eb063eda04a714ef89f066586a2c49588f7ca", + "reference": "eb6eb063eda04a714ef89f066586a2c49588f7ca", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.0 || ^0.7" + }, + "require-dev": { + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\Term\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Streaming terminal emulator, built on top of ReactPHP.", + "homepage": "https://github.com/clue/reactphp-term", + "keywords": [ + "C0", + "CSI", + "ansi", + "apc", + "ascii", + "c1", + "control codes", + "dps", + "osc", + "pm", + "reactphp", + "streaming", + "terminal", + "vt100", + "xterm" + ], + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2020-11-06T11:50:12+00:00" + }, + { + "name": "clue/utf8-react", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-utf8.git", + "reference": "8bc3f8c874cdf642c8f10f9ae93aadb8cd63da96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-utf8/zipball/8bc3f8c874cdf642c8f10f9ae93aadb8cd63da96", + "reference": "8bc3f8c874cdf642c8f10f9ae93aadb8cd63da96", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4 || ^0.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3 ||^5.7 || ^4.8", + "react/stream": "^1.0 || ^0.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\Utf8\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Streaming UTF-8 parser, built on top of ReactPHP.", + "homepage": "https://github.com/clue/reactphp-utf8", + "keywords": [ + "reactphp", + "streaming", + "unicode", + "utf-8", + "utf8" + ], + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2020-11-06T11:48:09+00:00" + }, { "name": "dealerdirect/phpcodesniffer-composer-installer", "version": "v0.7.0", "source": { "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "e8d808670b8f882188368faaf1144448c169c0b7" + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "e8d808670b8f882188368faaf1144448c169c0b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/e8d808670b8f882188368faaf1144448c169c0b7", + "reference": "e8d808670b8f882188368faaf1144448c169c0b7", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2 || ^3 || 4.0.x-dev" + }, + "require-dev": { + "composer/composer": "*", + "phpcompatibility/php-compatibility": "^9.0", + "sensiolabs/security-checker": "^4.1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "time": "2020-06-25T14:57:39+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-11-10T18:47:58+00:00" + }, + { + "name": "evenement/evenement", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7", + "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Evenement": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "time": "2017-07-23T21:35:13+00:00" + }, + { + "name": "jolicode/jolinotif", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/jolicode/JoliNotif.git", + "reference": "52f5b98f964f6009b8ec4c0e951edcd0862e2ac7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jolicode/JoliNotif/zipball/52f5b98f964f6009b8ec4c0e951edcd0862e2ac7", + "reference": "52f5b98f964f6009b8ec4c0e951edcd0862e2ac7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "symfony/process": "^3.3|^4.0|^5.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "symfony/finder": "^3.3|^4.0|^5.0", + "symfony/phpunit-bridge": "^3.4.26|^4.0|^5.0" + }, + "bin": [ + "jolinotif" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Joli\\JoliNotif\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Loïck Piera", + "email": "pyrech@gmail.com" + } + ], + "description": "Send desktop notifications on Windows, Linux, MacOS.", + "keywords": [ + "MAC", + "growl", + "linux", + "notification", + "windows" + ], + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/jolicode/jolinotif", + "type": "tidelift" + } + ], + "time": "2020-06-17T08:25:38+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.10.2", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-11-13T09:40:50+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^2.0", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2018-07-08T19:23:20+00:00" + }, + { + "name": "phar-io/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2018-07-08T19:19:57+00:00" + }, + { + "name": "phpcompatibility/php-compatibility", + "version": "9.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "homepage": "https://github.com/wimg", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "time": "2019-12-27T09:44:58+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2020-09-03T19:13:55+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2020-09-17T18:55:26+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.12.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "8ce87516be71aae9b956f81906aaf0338e0d8a2d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/8ce87516be71aae9b956f81906aaf0338e0d8a2d", + "reference": "8ce87516be71aae9b956f81906aaf0338e0d8a2d", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.1", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0", + "phpunit/phpunit": "^8.0 || ^9.0 <9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2020-09-29T09:10:42+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "6.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.1", + "phpunit/php-file-iterator": "^2.0", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^3.0", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.1 || ^4.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "suggest": { + "ext-xdebug": "^2.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2018-10-31T16:06:48+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "050bedf145a257b1ff02746c31894800e5122946" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", + "reference": "050bedf145a257b1ff02746c31894800e5122946", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2018-09-13T20:33:42+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "2.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2019-06-07T04:22:29+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "abandoned": true, + "time": "2019-09-17T06:23:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "7.5.20", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "9467db479d1b0487c99733bb1e7944d32deded2c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c", + "reference": "9467db479d1b0487c99733bb1e7944d32deded2c", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.7", + "phar-io/manifest": "^1.0.2", + "phar-io/version": "^2.0", + "php": "^7.1", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^6.0.7", + "phpunit/php-file-iterator": "^2.0.1", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^2.1", + "sebastian/comparator": "^3.0", + "sebastian/diff": "^3.0", + "sebastian/environment": "^4.0", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^2.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpunit/phpunit-mock-objects": "*" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*", + "phpunit/php-invoker": "^2.0" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2020-01-08T08:45:45+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "6d24de090cd59cfc830263cfba965be77b563c13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/6d24de090cd59cfc830263cfba965be77b563c13", + "reference": "6d24de090cd59cfc830263cfba965be77b563c13", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35" + }, + "suggest": { + "ext-event": "~1.0 for ExtEventLoop", + "ext-pcntl": "For signal handling support when using the StreamSelectLoop", + "ext-uv": "* for ExtUvLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "time": "2020-01-01T18:39:52+00:00" + }, + { + "name": "react/stream", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "7c02b510ee3f582c810aeccd3a197b9c2f52ff1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/7c02b510ee3f582c810aeccd3a197b9c2f52ff1a", + "reference": "7c02b510ee3f582c810aeccd3a197b9c2f52ff1a", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "time": "2020-05-04T10:17:57+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "shasum": "" + }, + "require": { + "php": "^7.1", + "sebastian/diff": "^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-07-12T15:12:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "time": "2019-02-04T06:01:07+00:00" + }, + { + "name": "sebastian/environment", + "version": "4.2.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368", + "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2019-11-20T08:46:58+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2019-09-14T09:02:43+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2018-10-04T04:07:39+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "sirbrillig/phpcs-variable-analysis", + "version": "v2.9.0", + "source": { + "type": "git", + "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git", + "reference": "ff54d4ec7f2bd152d526fdabfeff639aa9b8be01" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/ff54d4ec7f2bd152d526fdabfeff639aa9b8be01", + "reference": "ff54d4ec7f2bd152d526fdabfeff639aa9b8be01", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "squizlabs/php_codesniffer": "^3.1" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4 || ^0.5 || ^0.6", + "limedeck/phpunit-detailed-printer": "^3.1 || ^4.0 || ^5.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^5.0 || ^6.5 || ^7.0 || ^8.0", + "sirbrillig/phpcs-import-detection": "^1.1" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "VariableAnalysis\\": "VariableAnalysis/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Sam Graham", + "email": "php-codesniffer-variableanalysis@illusori.co.uk" + }, + { + "name": "Payton Swick", + "email": "payton@foolord.com" + } + ], + "description": "A PHPCS sniff to detect problems with variables.", + "time": "2020-10-07T23:32:29+00:00" + }, + { + "name": "spatie/phpunit-watcher", + "version": "1.23.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/phpunit-watcher.git", + "reference": "8a8e0c3c8f3f03dfdb6bf62abf89c1b7273fc0b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/phpunit-watcher/zipball/8a8e0c3c8f3f03dfdb6bf62abf89c1b7273fc0b3", + "reference": "8a8e0c3c8f3f03dfdb6bf62abf89c1b7273fc0b3", + "shasum": "" + }, + "require": { + "clue/stdio-react": "^2.0", + "jolicode/jolinotif": "^2.0", + "php": "^7.2", + "symfony/console": "^4.0|^5.0", + "symfony/process": "^4.0|^5.0", + "symfony/yaml": "^4.0|^5.0", + "yosymfony/resource-watcher": "^2.0" + }, + "conflict": { + "yosymfony/resource-watcher": "<2.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.0" + }, + "bin": [ + "phpunit-watcher" + ], + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\PhpUnitWatcher\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Automatically rerun PHPUnit tests when source code changes", + "homepage": "https://github.com/spatie/phpunit-watcher", + "keywords": [ + "phpunit-watcher", + "spatie" + ], + "time": "2020-10-27T07:36:25+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.5.8", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "9d583721a7157ee997f235f327de038e7ea6dac4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/9d583721a7157ee997f235f327de038e7ea6dac4", + "reference": "9d583721a7157ee997f235f327de038e7ea6dac4", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2020-10-23T02:01:07+00:00" + }, + { + "name": "symfony/console", + "version": "v5.1.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "e0b2c29c0fa6a69089209bbe8fcff4df2a313d0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/e0b2c29c0fa6a69089209bbe8fcff4df2a313d0e", + "reference": "e0b2c29c0fa6a69089209bbe8fcff4df2a313d0e", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2", + "symfony/string": "^5.1" + }, + "conflict": { + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-24T12:01:57+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5fa56b4074d1ae755beb55617ddafe6f5d78f665", + "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-07T11:33:47+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.1.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "e70eb5a69c2ff61ea135a13d2266e8914a67b3a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/e70eb5a69c2ff61ea135a13d2266e8914a67b3a0", + "reference": "e70eb5a69c2ff61ea135a13d2266e8914a67b3a0", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-24T12:01:57+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.20.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "f4ba089a5b6366e453971d3aad5fe8e897b37f41" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f4ba089a5b6366e453971d3aad5fe8e897b37f41", + "reference": "f4ba089a5b6366e453971d3aad5fe8e897b37f41", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.20-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-23T14:02:19+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.20.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "c7cf3f858ec7d70b89559d6e6eb1f7c2517d479c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/c7cf3f858ec7d70b89559d6e6eb1f7c2517d479c", + "reference": "c7cf3f858ec7d70b89559d6e6eb1f7c2517d479c", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.20-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-23T14:02:19+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.20.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "727d1096295d807c309fb01a851577302394c897" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/727d1096295d807c309fb01a851577302394c897", + "reference": "727d1096295d807c309fb01a851577302394c897", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.20-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-23T14:02:19+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.20.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "39d483bdf39be819deabf04ec872eb0b2410b531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/39d483bdf39be819deabf04ec872eb0b2410b531", + "reference": "39d483bdf39be819deabf04ec872eb0b2410b531", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.20-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-23T14:02:19+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.20.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "8ff431c517be11c78c48a39a66d37431e26a6bed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/8ff431c517be11c78c48a39a66d37431e26a6bed", + "reference": "8ff431c517be11c78c48a39a66d37431e26a6bed", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.20-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-23T14:02:19+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.20.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "e70aa8b064c5b72d3df2abd5ab1e90464ad009de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/e70aa8b064c5b72d3df2abd5ab1e90464ad009de", + "reference": "e70aa8b064c5b72d3df2abd5ab1e90464ad009de", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.20-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-23T14:02:19+00:00" + }, + { + "name": "symfony/process", + "version": "v5.1.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "f00872c3f6804150d6a0f73b4151daab96248101" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/f00872c3f6804150d6a0f73b4151daab96248101", + "reference": "f00872c3f6804150d6a0f73b4151daab96248101", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-24T12:01:57+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/e8d808670b8f882188368faaf1144448c169c0b7", - "reference": "e8d808670b8f882188368faaf1144448c169c0b7", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1", "shasum": "" }, "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2 || ^3 || 4.0.x-dev" + "php": ">=7.2.5", + "psr/container": "^1.0" }, - "require-dev": { - "composer/composer": "*", - "phpcompatibility/php-compatibility": "^9.0", - "sensiolabs/security-checker": "^4.1.0" + "suggest": { + "symfony/service-implementation": "" }, - "type": "composer-plugin", + "type": "library", "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + "branch-alias": { + "dev-master": "2.2-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } }, "autoload": { "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + "Symfony\\Contracts\\Service\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -184,189 +2971,286 @@ ], "authors": [ { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], - "time": "2020-06-25T14:57:39+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-07T11:33:47+00:00" }, { - "name": "phpcompatibility/php-compatibility", - "version": "9.3.5", + "name": "symfony/string", + "version": "v5.1.8", "source": { "type": "git", - "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", - "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" + "url": "https://github.com/symfony/string.git", + "reference": "a97573e960303db71be0dd8fda9be3bca5e0feea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", - "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", + "url": "https://api.github.com/repos/symfony/string/zipball/a97573e960303db71be0dd8fda9be3bca5e0feea", + "reference": "a97573e960303db71be0dd8fda9be3bca5e0feea", "shasum": "" }, "require": { - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" - }, - "conflict": { - "squizlabs/php_codesniffer": "2.6.2" + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" }, "require-dev": { - "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + "symfony/error-handler": "^4.4|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" }, - "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", - "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "files": [ + "Resources/functions.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] }, - "type": "phpcodesniffer-standard", "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0-or-later" + "MIT" ], "authors": [ { - "name": "Wim Godden", - "homepage": "https://github.com/wimg", - "role": "lead" - }, - { - "name": "Juliette Reinders Folmer", - "homepage": "https://github.com/jrfnl", - "role": "lead" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Contributors", - "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", - "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "description": "Symfony String component", + "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "phpcs", - "standards" + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" ], - "time": "2019-12-27T09:44:58+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-24T12:01:57+00:00" }, { - "name": "sirbrillig/phpcs-variable-analysis", - "version": "v2.9.0", + "name": "symfony/yaml", + "version": "v5.1.8", "source": { "type": "git", - "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git", - "reference": "ff54d4ec7f2bd152d526fdabfeff639aa9b8be01" + "url": "https://github.com/symfony/yaml.git", + "reference": "f284e032c3cefefb9943792132251b79a6127ca6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/ff54d4ec7f2bd152d526fdabfeff639aa9b8be01", - "reference": "ff54d4ec7f2bd152d526fdabfeff639aa9b8be01", + "url": "https://api.github.com/repos/symfony/yaml/zipball/f284e032c3cefefb9943792132251b79a6127ca6", + "reference": "f284e032c3cefefb9943792132251b79a6127ca6", "shasum": "" }, "require": { - "php": ">=5.4.0", - "squizlabs/php_codesniffer": "^3.1" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<4.4" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4 || ^0.5 || ^0.6", - "limedeck/phpunit-detailed-printer": "^3.1 || ^4.0 || ^5.0", - "phpstan/phpstan": "^0.11.8", - "phpunit/phpunit": "^5.0 || ^6.5 || ^7.0 || ^8.0", - "sirbrillig/phpcs-import-detection": "^1.1" + "symfony/console": "^4.4|^5.0" }, - "type": "phpcodesniffer-standard", + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", "autoload": { "psr-4": { - "VariableAnalysis\\": "VariableAnalysis/" - } + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-2-Clause" + "MIT" ], "authors": [ { - "name": "Sam Graham", - "email": "php-codesniffer-variableanalysis@illusori.co.uk" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Payton Swick", - "email": "payton@foolord.com" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "A PHPCS sniff to detect problems with variables.", - "time": "2020-10-07T23:32:29+00:00" + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-24T12:03:25+00:00" }, { - "name": "squizlabs/php_codesniffer", - "version": "3.5.8", + "name": "theseer/tokenizer", + "version": "1.2.0", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "9d583721a7157ee997f235f327de038e7ea6dac4" + "url": "https://github.com/theseer/tokenizer.git", + "reference": "75a63c33a8577608444246075ea0af0d052e452a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/9d583721a7157ee997f235f327de038e7ea6dac4", - "reference": "9d583721a7157ee997f235f327de038e7ea6dac4", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", + "reference": "75a63c33a8577608444246075ea0af0d052e452a", "shasum": "" }, "require": { - "ext-simplexml": "*", + "ext-dom": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", - "php": ">=5.4.0" + "php": "^7.2 || ^8.0" }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } ], + "time": "2020-07-12T23:59:07+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Greg Sherwood", - "role": "lead" + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "description": "Assertions to validate method input/output with nice error messages.", "keywords": [ - "phpcs", - "standards" + "assert", + "check", + "validate" ], - "time": "2020-10-23T02:01:07+00:00" + "time": "2020-07-08T17:02:28+00:00" }, { "name": "wp-coding-standards/wpcs", @@ -416,7 +3300,7 @@ }, { "name": "wp-phpunit/wp-phpunit", - "version": "5.5.1", + "version": "5.5.3", "source": { "type": "git", "url": "https://github.com/wp-phpunit/wp-phpunit.git", @@ -456,6 +3340,59 @@ "wordpress" ], "time": "2020-09-02T15:53:50+00:00" + }, + { + "name": "yosymfony/resource-watcher", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/yosymfony/resource-watcher.git", + "reference": "a8c34f704e6bd4f786c97f3c0ba65bd86cb2bd73" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yosymfony/resource-watcher/zipball/a8c34f704e6bd4f786c97f3c0ba65bd86cb2bd73", + "reference": "a8c34f704e6bd4f786c97f3c0ba65bd86cb2bd73", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "symfony/finder": "^2.7|^3.0|^4.0|^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7", + "symfony/filesystem": "^2.7|^3.0|^4.0|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Yosymfony\\ResourceWatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Victor Puertas", + "email": "vpgugr@gmail.com" + } + ], + "description": "A simple resource watcher using Symfony Finder", + "homepage": "http://yosymfony.com", + "keywords": [ + "finder", + "resources", + "symfony", + "watcher" + ], + "time": "2020-01-04T15:36:55+00:00" } ], "aliases": [], diff --git a/docs/architecture/data-flow.md b/docs/architecture/data-flow.md index 623a9c3530f77b..3fefec3aae7e77 100644 --- a/docs/architecture/data-flow.md +++ b/docs/architecture/data-flow.md @@ -29,7 +29,7 @@ const block = { } ``` -Note the attributes keys and types, the allowed inner blocks are defined by the block type. For example, the core quote block has a `cite` string attribute reprensing the cite content while a heading block has a numeric `level` attribute, representing the level of the heading (1 to 6). +Note the attributes keys and types, the allowed inner blocks are defined by the block type. For example, the core quote block has a `cite` string attribute representing the cite content while a heading block has a numeric `level` attribute, representing the level of the heading (1 to 6). During the lifecycle of the block in the editor, the block object can receive extra metadata: diff --git a/docs/architecture/folder-structure.md b/docs/architecture/folder-structure.md index 65c711e6f849a1..6d9b9c6163764a 100644 --- a/docs/architecture/folder-structure.md +++ b/docs/architecture/folder-structure.md @@ -7,7 +7,6 @@ The following snippet explains how the Gutenberg repository is structured omitti ├── README.md ├── SECURITY.md ├── CONTRIBUTING.md - ├── CONTRIBUTORS.md ├── CODE_OF_CONDUCT.md │ ├── .editorconfig diff --git a/docs/architecture/fse-templates.md b/docs/architecture/fse-templates.md new file mode 100644 index 00000000000000..293b7f1f7bd303 --- /dev/null +++ b/docs/architecture/fse-templates.md @@ -0,0 +1,44 @@ +### Template and template part flows + +> This is the documentation for the current implementation of the block-based templates and template parts themes. This is part of the Full Site Editing project. These features are still experimental in the plugin. “Experimental” means this is just an early implementation that is subject to potential drastic and breaking changes in iterations based on feedback from users, contributors, and theme authors. + +This document will explain the internals of how templates and templates parts are rendered in the frontend and edited in the backend. For an introduction about block-based themes and Full site editing templates, refer to the [block-based themes documentation](/docs/designers-developers/developers/themes/block-based-themes.md). + +## Storage + +Just like the regular templates, the block-based templates live initially as files in the theme folder but the main difference is that the user can edit these templates in the UI in the Site Editor. + +When a user edits a template (or template-part), the initial theme template file is kept as is but a forked version of the template is saved to the `wp_template` custom post type (or `wp_template_part` for template parts). + +These capabilities mean that at any point in time, a mix of template files (from the theme) and CPT templates (the edited templates) are used to render the frontend of the site. + +## Synchronization + +In order to simplify the algorithm used to edit and render the templates from two different places, we performed an operation called "template synchronization". + +The synchronization consists of duplicating the theme templates in the `wp_template` (and `wp_template_part`) custom templates with an `auto-draft` status. When a user edits these templates, the status is updated to `publish`. + +This means: + + - The rendering/fetching of templates only need to consider the custom post type templates. It is not necessary to fetch the template files from the theme folder directly. The synchronization will ensure these are duplicated in the CPT. + - Untouched theme templates have the `auto-draft` status. + - Edited theme templates have the `publish` status. + +The synchronization is important for two different flows: + + - When editing the template and template parts, the site editor frontend fetches the edited and available templates through the REST API. This means that for all `GET` API requests performed to the `wp-templates` and `wp-template-parts` endpoint synchronization is required. + - When rendering a template (sometimes referred to as "resolving a template"): this is the algorithm that WordPress follows to traverse the template hierarchy and find the right template to render for the current page being loaded. + - When exporting a block-based theme, we need to export all its templates back as files. The synchronization is required to simplify the operation and only export the CPT templates. + +## Switching themes + +Since block-based themes make use of templates that can refer to each other and that can be saved to a custom post type, it becomes possible to mix templates and template parts from different themes. For example: + + - A user might like the "header" template part of theme A and would like to use it in theme B. + - A user might like the "contact" template from theme A and would like to use it in theme B. + +Enabling these flows will require well thought UIs and experience. For the current phase of Full-site editing, we're starting by forbidding these possibilities and making template and template-parts theme specific. + +That said, it is still important to keep track of where the template and template part come from initially. From which theme, it's based. We do so by saving a `theme` post meta containing the theme identifier for each template and template part CPT entry. + +In the future, we might consider allowing the user to mix template and template parts with different `theme` post meta values. diff --git a/docs/architecture/readme.md b/docs/architecture/readme.md index 20bb2f1a340697..c0b214d6147fed 100644 --- a/docs/architecture/readme.md +++ b/docs/architecture/readme.md @@ -10,3 +10,4 @@ Let’s look at the big picture and the architectural and UX principles of the b - What are the decision decisions behind the Data Module? - [Why is Puppeteer the tool of choice for end-to-end tests?](/docs/architecture/automated-testing.md) - [What's the difference between the different editor packages? What's the purpose of each package?](/docs/architecture/modularity.md#whats-the-difference-between-the-different-editor-packages-whats-the-purpose-of-each-package) +- [Template and template parts flows](/docs/architecture/fse-templates.md) diff --git a/docs/contributors/coding-guidelines.md b/docs/contributors/coding-guidelines.md index 5fea9211b10e6a..19e7702cfbad98 100644 --- a/docs/contributors/coding-guidelines.md +++ b/docs/contributors/coding-guidelines.md @@ -65,7 +65,7 @@ Examples of styles that appear in both the theme and the editor include gallery ## JavaScript -JavaScript in Gutenberg uses modern language features of the [ECMAScript language specification](https://www.ecma-international.org/ecma-262/) as well as the [JSX language syntax extension](https://reactjs.org/docs/introducing-jsx.html). These are enabled through a combination of preset configurations, notably [`@wordpress/babel-preset-default`](https://github.com/WordPress/gutenberg/tree/master/packages/babel-preset-default) which is used as a preset in the project's [Babel](https://babeljs.io/) configuration. +JavaScript in Gutenberg uses modern language features of the [ECMAScript language specification](https://www.ecma-international.org/ecma-262/) as well as the [JSX language syntax extension](https://reactjs.org/docs/introducing-jsx.html). These are enabled through a combination of preset configurations, notably [`@wordpress/babel-preset-default`](https://github.com/WordPress/gutenberg/tree/master/packages/babel-preset-default) which is used as a preset in the project's [Babel](https://babeljs.io/) configuration. While the [staged process](https://tc39.es/process-document/) for introducing a new JavaScript language feature offers an opportunity to use new features before they are considered complete, **the Gutenberg project and the `@wordpress/babel-preset-default` configuration will only target support for proposals which have reached Stage 4 ("Finished")**. @@ -144,6 +144,7 @@ While an experimental API may often stabilize into a publicly-available API, the When possible, use [shorthand notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#New_notations_in_ECMAScript_2015) when defining object property values: + ```js const a = 10; @@ -172,9 +173,10 @@ String literals should be declared with single-quotes _unless_ the string itself In general, avoid backslash-escaping quotes: + ```js // Bad: -const name = 'Matt'; +const name = "Matt"; // Good: const name = 'Matt'; @@ -190,6 +192,7 @@ const oddString = "She said 'This is odd.'"; You should use ES6 Template Strings over string concatenation whenever possible: + ```js const name = 'Stacey'; @@ -203,13 +206,13 @@ alert( `My name is ${ name }.` ); [Optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) is a new language feature introduced in version 2020 of the ECMAScript specification. While the feature can be very convenient for property access on objects which are potentially null-ish (`null` or `undefined`), there are a number of common pitfalls to be aware of when using optional chaining. These may be issues that linting and/or type-checking can help protect against at some point in the future. In the meantime, you will want to be cautious of the following items: -- When negating (`!`) the result of a value which is evaluated with optional chaining, you should be observant that in the case that optional chaining reaches a point where it cannot proceed, it will produce a [falsy value](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) that will be transformed to `true` when negated. In many cases, this is not an expected result. - - Example: `const hasFocus = ! nodeRef.current?.contains( document.activeElement );` will yield `true` if `nodeRef.current` is not assigned. - - See related issue: [#21984](https://github.com/WordPress/gutenberg/issues/21984) - - See similar ESLint rule: [`no-unsafe-negation`](https://eslint.org/docs/rules/no-unsafe-negation) -- When assigning a boolean value, observe that optional chaining may produce values which are [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) (`undefined`, `null`), but not strictly `false`. This can become an issue when the value is passed around in a way where it is expected to be a boolean (`true` or `false`). While it's a common occurrence for booleans—since booleans are often used in ways where the logic considers truthiness and falsyness broadly—these issues can also occur for other optional chaining when eagerly assuming a type resulting from the end of the property access chain. [Type-checking](https://github.com/WordPress/gutenberg/blob/master/packages/README.md#typescript) may help in preventing these sorts of errors. - - Example: `document.body.classList.toggle( 'has-focus', nodeRef.current?.contains( document.activeElement ) );` may wrongly _add_ the class, since [the second argument is optional](https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/toggle). If `undefined` is passed, it would not unset the class as it would when `false` is passed. - - Example: `` may inadvertently cause warnings in React by toggling between [controlled and uncontrolled inputs](https://reactjs.org/docs/uncontrolled-components.html). This is an easy trap to fall into when eagerly assuming that a result of `trim()` will always return a string value, overlooking the fact the optional chaining may have caused evaluation to abort earlier with a value of `undefined`. +- When negating (`!`) the result of a value which is evaluated with optional chaining, you should be observant that in the case that optional chaining reaches a point where it cannot proceed, it will produce a [falsy value](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) that will be transformed to `true` when negated. In many cases, this is not an expected result. + - Example: `const hasFocus = ! nodeRef.current?.contains( document.activeElement );` will yield `true` if `nodeRef.current` is not assigned. + - See related issue: [#21984](https://github.com/WordPress/gutenberg/issues/21984) + - See similar ESLint rule: [`no-unsafe-negation`](https://eslint.org/docs/rules/no-unsafe-negation) +- When assigning a boolean value, observe that optional chaining may produce values which are [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) (`undefined`, `null`), but not strictly `false`. This can become an issue when the value is passed around in a way where it is expected to be a boolean (`true` or `false`). While it's a common occurrence for booleans—since booleans are often used in ways where the logic considers truthiness and falsyness broadly—these issues can also occur for other optional chaining when eagerly assuming a type resulting from the end of the property access chain. [Type-checking](https://github.com/WordPress/gutenberg/blob/master/packages/README.md#typescript) may help in preventing these sorts of errors. + - Example: `document.body.classList.toggle( 'has-focus', nodeRef.current?.contains( document.activeElement ) );` may wrongly _add_ the class, since [the second argument is optional](https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/toggle). If `undefined` is passed, it would not unset the class as it would when `false` is passed. + - Example: `` may inadvertently cause warnings in React by toggling between [controlled and uncontrolled inputs](https://reactjs.org/docs/uncontrolled-components.html). This is an easy trap to fall into when eagerly assuming that a result of `trim()` will always return a string value, overlooking the fact the optional chaining may have caused evaluation to abort earlier with a value of `undefined`. ### `@wordpress/element` (React) Components @@ -372,7 +375,7 @@ Similarly, use the `undefined` type only if you're expecting an explicit value o */ ``` -If a parameter is optional, use the [square-bracket notation](https://jsdoc.app/tags-param.html#optional-parameters-and-default-values). If an optional parameter has a default value which can be expressed as a [default parameter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters) in the function expression, it is not necesssary to include the value in JSDoc. If the function parameter has an effective default value which requires complex logic and cannot be expressed using the default parameters syntax, you can choose to include the default value in the JSDoc. +If a parameter is optional, use the [square-bracket notation](https://jsdoc.app/tags-param.html#optional-parameters-and-default-values). If an optional parameter has a default value which can be expressed as a [default parameter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters) in the function expression, it is not necessary to include the value in JSDoc. If the function parameter has an effective default value which requires complex logic and cannot be expressed using the default parameters syntax, you can choose to include the default value in the JSDoc. ```js /** diff --git a/docs/contributors/develop.md b/docs/contributors/develop.md index f41b182ac31328..01c015b37fc239 100644 --- a/docs/contributors/develop.md +++ b/docs/contributors/develop.md @@ -6,7 +6,7 @@ A guide on how to get started contributing code to the Gutenberg project. The [Make WordPress Core blog](https://make.wordpress.org/core/) is the primary spot for the latest information around WordPress development: including announcements, product goals, meeting notes, meeting agendas, and more. -Real-time discussions for development take place in `#core-editor` and `#core-js` channels in [Make WordPress Slack](https://make.wordpress.org/chat) (registration required). Weekly meetings for the editor component are on Wednesdays at 14:00UTC, and for the JavaScript component on Tuesday at 14:00UTC, in their respective Slack channels. +Real-time discussions for development take place in `#core-editor` and `#core-js` channels in [Make WordPress Slack](https://make.wordpress.org/chat) (registration required). Weekly meetings for the editor component are on Wednesdays at 14:00UTC, and for the JavaScript component on Tuesday at 15:00UTC, in their respective Slack channels. ## Development Hub diff --git a/docs/contributors/document.md b/docs/contributors/document.md index 4419e5f689eedc..2845f4e8b6e0ba 100644 --- a/docs/contributors/document.md +++ b/docs/contributors/document.md @@ -47,7 +47,7 @@ To add a new documentation page requires a working JavaScript development enviro 4. Run `npm run docs:build` to update `manifest.json`. 5. Commit `manifest.json` with other files updated. -If you forget to run, `npm run docs:build` your PR will fail the static analysis check, since the `manifest.json` file is an uncommited local change that must be commited. +If you forget to run, `npm run docs:build` your PR will fail the static analysis check, since the `manifest.json` file is an uncommitted local change that must be committed. ### Using Links diff --git a/docs/contributors/getting-started-native-mobile.md b/docs/contributors/getting-started-native-mobile.md index ed4d993a0fae0a..b794f70267be14 100644 --- a/docs/contributors/getting-started-native-mobile.md +++ b/docs/contributors/getting-started-native-mobile.md @@ -27,7 +27,7 @@ Before running the demo app, you need to download and install the project depend ``` nvm install --latest-npm -npm install +npm ci ``` ## Run diff --git a/docs/contributors/getting-started.md b/docs/contributors/getting-started.md index 65c5269f7264fc..ddd38be4d16931 100644 --- a/docs/contributors/getting-started.md +++ b/docs/contributors/getting-started.md @@ -1,10 +1,10 @@ # Getting Started -The following guide is for setting up your local environment to contribute to the Gutenberg project. There is significant overlap between an environment to contribute and an environment used to extend the WordPress block editor. You can review the [Development Enviornment tutorial](/docs/designers-developers/developers/tutorials/devenv/readme.md) for additional setup information. +The following guide is for setting up your local environment to contribute to the Gutenberg project. There is significant overlap between an environment to contribute and an environment used to extend the WordPress block editor. You can review the [Development Environment tutorial](/docs/designers-developers/developers/tutorials/devenv/readme.md) for additional setup information. ## Development Tools (Node) -Gutenberg is a JavaScript project and requires [Node.js](https://nodejs.org/). The project is built using the latest active LTS release of node, and the latest verion of NPM. See the [LTS release schedule](https://github.com/nodejs/Release#release-schedule) for details. +Gutenberg is a JavaScript project and requires [Node.js](https://nodejs.org/). The project is built using the latest active LTS release of node, and the latest version of NPM. See the [LTS release schedule](https://github.com/nodejs/Release#release-schedule) for details. We recommend using the [Node Version Manager](https://github.com/nvm-sh/nvm) (nvm) since it is the easiest way to install and manage node for macOS, Linux, and Windows 10 using WSL2. See [our Development Tools guide](/docs/designers-developers/developers/tutorials/devenv/readme.md#development-tools) or the Nodejs site for additional installation instructions. @@ -12,7 +12,7 @@ After installing Node, you can build Gutenberg by running the following from wit ```bash -npm install +npm ci npm run build ``` @@ -30,11 +30,11 @@ If you do not have a local WordPress environment setup, follow the steps in the ### Using wp-env to Install a Local Environment -The [wp-env package](/packages/env/README.md) was developed with the Gutneberg project as a quick way to create a standard WordPress environment using Docker. It is also published as the `@wordpress/env` npm package. +The [wp-env package](/packages/env/README.md) was developed with the Gutenberg project as a quick way to create a standard WordPress environment using Docker. It is also published as the `@wordpress/env` npm package. -By default, `wp-env` can run in a plugin direcotry to create and run a WordPress enviornment, mounting and activating the plugin automatically. You can also configure `wp-env` to use existing installs, multiple plugins, or themes. See the [wp-env package](/packages/env/README.md#wp-envjson) for complete documentation. +By default, `wp-env` can run in a plugin directory to create and run a WordPress environment, mounting and activating the plugin automatically. You can also configure `wp-env` to use existing installs, multiple plugins, or themes. See the [wp-env package](/packages/env/README.md#wp-envjson) for complete documentation. -If you don't already have it, you'll need to install Docker and Docker Compose in order to use `wp-env`. See the [Developement Environment tutorial for additional details](/docs/designers-developers/developers/tutorials/devenv/readme.md). +If you don't already have it, you'll need to install Docker and Docker Compose in order to use `wp-env`. See the [Development Environment tutorial for additional details](/docs/designers-developers/developers/tutorials/devenv/readme.md). Once Docker is installed and running: To install WordPress, run the following from within the cloned gutenberg directory: @@ -120,14 +120,11 @@ If so, you need to instruct Apache to allow following such links: Tools like MAMP tend to configure MySQL to use ports other than the default 3306, often preferring 8889. This may throw off WP-CLI, which will fail after trying to connect to the database. To remedy this, edit `wp-config.php` and change the `DB_HOST` constant from `define( 'DB_HOST', 'localhost' )` to `define( 'DB_HOST', '127.0.0.1:8889' )`. -## On A Remote Server -======= ### On A Remote Server ->>>>>>> Docs: Refresh Getting Started guide You can use a remote server in development by building locally and then uploading the built files as a plugin to the remote server. -To build: open a terminal (or if on Windows, a command prompt) and navigate to the repository you cloned. Now type `npm install` to get the dependencies all set up. Once that finishes, you can type `npm run build`. +To build: open a terminal (or if on Windows, a command prompt) and navigate to the repository you cloned. Now type `npm ci` to get the dependencies all set up. Once that finishes, you can type `npm run build`. After building the cloned gutenberg directory contains the complete plugin, you can upload the entire repository to your `wp-content/plugins` directory and activate the plugin from the WordPress admin. @@ -167,7 +164,7 @@ With the extension installed, ESLint will use the [.eslintrc.js](https://github. [Prettier](https://prettier.io/) is a tool that allows you to define an opinionated format, and automate fixing the code to match that format. Prettier and ESlint are similar, Prettier is more about formatting and style, while ESlint is for detecting coding errors. -To use Prettier with Visual Studio Code, you should install the [Prettier - Code formatter extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode). You can then configure it to be the default formatter and to automatically fix issues on save, by adding the following to your settings. __**Note**: depending on where you are viewing htis document, the brackets may show as double, the proper format is just a single bracket.__ +To use Prettier with Visual Studio Code, you should install the [Prettier - Code formatter extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode). You can then configure it to be the default formatter and to automatically fix issues on save, by adding the following to your settings. __**Note**: depending on where you are viewing this document, the brackets may show as double, the proper format is just a single bracket.__ ```json "[[javascript]]": { diff --git a/docs/contributors/git-workflow.md b/docs/contributors/git-workflow.md index 0894945e8a2850..2e4926d593578a 100644 --- a/docs/contributors/git-workflow.md +++ b/docs/contributors/git-workflow.md @@ -71,7 +71,7 @@ git push -u origin upgrade/my-branch **Step 9**: Keep up with new activity on the pull request. If any additional changes or updates are requested, then make the changes locally and push them up, following Steps 4-6. -Do not make a new pull request for updates; by pushing your change to your respotiroy it will update the same PR. In this sense, the PR is a pointer on the WordPress Gutenberg repository to your copy. So when you update your copy, the PR is also updated. +Do not make a new pull request for updates; by pushing your change to your repository it will update the same PR. In this sense, the PR is a pointer on the WordPress Gutenberg repository to your copy. So when you update your copy, the PR is also updated. That’s it! Once approved and merged, your change will be incorporated into the main repository. 🎉 diff --git a/docs/contributors/release.md b/docs/contributors/release.md index 750e6ec17c05d0..97bf6d2acb23e3 100644 --- a/docs/contributors/release.md +++ b/docs/contributors/release.md @@ -233,7 +233,7 @@ If you don't have access to [make.wordpress.org/core](https://make.wordpress.org The Gutenberg repository mirrors the [WordPress SVN repository](https://make.wordpress.org/core/handbook/about/release-cycle/) in terms of branching for each SVN branch, a corresponding Gutenberg `wp/*` branch is created: - The `wp/trunk` branch contains all the packages that are published and used in the `trunk` branch of WordPress. -- A Gutenberg branch targeting a specific WordPress major release (including its further minor increments) is created (example `wp/5.2`) based on the `wp/trunk` Gutenberg branch when the WordPress `trunk` branch is marked as "feature-freezed". (This usually happens when the first `beta` of the next WordPress major version is released). +- A Gutenberg branch targeting a specific WordPress major release (including its further minor increments) is created (example `wp/5.2`) based on the `wp/trunk` Gutenberg branch when the corresponding WordPress release branch is created. (This usually happens when the first `RC` of the next WordPress major version is released). ### Synchronizing WordPress Trunk @@ -249,7 +249,7 @@ The first step is automated via `./bin/plugin/cli.js npm-stable` command. You on 4. Remove all files from the current branch: `git rm -r .`. 5. Check out all the files from the release branch: `git checkout release/x.x -- .`. 6. Commit all changes to the `wp/trunk` branch with `git commit -m "Merge changes published in the Gutenberg plugin vX.X release"` and push to the repository. -7. Update the `CHANGELOG.md` files of the packages with the new publish version calculated and commit to the `wp/trunk` branch. Assuming the package versions are written using this format `major.minor.patch`, make sure to bump at least the `minor` version number. For example, if the CHANGELOG of the package to be released indicates that the next unreleased version is `5.6.1`, choose `5.7.0` as a version in case of `minor` version. +7. Update the `CHANGELOG.md` files of the packages with the new publish version calculated and commit to the `wp/trunk` branch. Assuming the package versions are written using this format `major.minor.patch`, make sure to bump at least the `minor` version number. For example, if the CHANGELOG of the package to be released indicates that the next unreleased version is `5.6.1`, choose `5.7.0` as a version in case of `minor` version. This is important as the patch version numbers should be reserved in case bug fixes are needed for a minor WordPress release (see below). Once the command is finished, you can start the second part: publishing the npm packages. @@ -270,15 +270,16 @@ The following workflow is needed when bug fixes or security releases need to be - During the `beta` and the `RC` period of the WordPress release cycle. - For WordPress minor releases and WordPress security releases (example `5.1.1`). -1. Cherry-pick -2. Check out the last published Gutenberg release branch `git checkout release/x.x` -3. Create a Pull Request from this branch targeting the WordPress related major branch (Example `wp/5.2`). +1. Check out the relevant WordPress major branch (If the minor release is 5.2.1, check out `wp/5.2`). +2. Create a feature branch from that branch, and cherry-pick the merge commits for the needed bug fixes onto it. +3. Create a Pull Request from this branch targeting the WordPress major branch used above. 4. Merge the Pull Request using the "Rebase and Merge" button to keep the history of the commits. Now, the branch is ready to be used to publish the npm packages. 1. Check out the WordPress branch used before (Example `wp/5.2`). -2. Run the [package release process] but when asked for the version numbers to choose for each package, (assuming the package versions are written using this format `major.minor.patch`) make sure to bump only the `patch` version number. For example, if the last published package version for this WordPress branch was `5.6.0`, choose `5.6.1` as a version. +2. `git pull`. +3. Run the [package release process] but when asked for the version numbers to choose for each package, (assuming the package versions are written using this format `major.minor.patch`) make sure to bump only the `patch` version number. For example, if the last published package version for this WordPress branch was `5.6.0`, choose `5.6.1` as a version. **Note:** For WordPress `5.0` and WordPress `5.1`, a different release process was used. This means that when choosing npm package versions targeting these two releases, you won't be able to use the next `patch` version number as it may have been already used. You should use the "metadata" modifier for these. For example, if the last published package version for this WordPress branch was `5.6.1`, choose `5.6.1+patch.1` as a version. @@ -311,7 +312,7 @@ Now _cherry-pick_ the commits from `master` to `wp/trunk`, use `-m 1 commithash` 1. `git cherry-pick -m 1 cb150a2` 2. `git push` -Whilst waiting for the Travis CI build for `wp/trunk` [branch to pass](https://travis-ci.com/WordPress/gutenberg/branches) identify and begin updating the `CHANGELOG.md` files: +Whilst waiting for the GitHub actions build for `wp/trunk`[branch to pass](https://github.com/WordPress/gutenberg/actions?query=branch%3Awp%2Ftrunk), identify and begin updating the `CHANGELOG.md` files: 1. `git checkout wp/trunk` 2. `npm run publish:check` > Example diff --git a/docs/contributors/testing-overview.md b/docs/contributors/testing-overview.md index 1c294f216c859f..c5d21a281e5df7 100644 --- a/docs/contributors/testing-overview.md +++ b/docs/contributors/testing-overview.md @@ -19,7 +19,9 @@ When writing tests consider the following: ## JavaScript Testing -Tests for JavaScript use [Jest](https://jestjs.io/) as the test runner and its API for [globals](https://jestjs.io/docs/en/api.html) (`describe`, `test`, `beforeEach` and so on) [assertions](https://jestjs.io/docs/en/expect.html), [mocks](https://jestjs.io/docs/en/mock-functions.html), [spies](https://jestjs.io/docs/en/jest-object.html#jestspyonobject-methodname) and [mock functions](https://jestjs.io/docs/en/mock-function-api.html). If needed, you can also use [Enzyme](https://github.com/airbnb/enzyme) for React component testing. +Tests for JavaScript use [Jest](https://jestjs.io/) as the test runner and its API for [globals](https://jestjs.io/docs/en/api.html) (`describe`, `test`, `beforeEach` and so on) [assertions](https://jestjs.io/docs/en/expect.html), [mocks](https://jestjs.io/docs/en/mock-functions.html), [spies](https://jestjs.io/docs/en/jest-object.html#jestspyonobject-methodname) and [mock functions](https://jestjs.io/docs/en/mock-function-api.html). If needed, you can also use [React Testing Library](https://testing-library.com/docs/react-testing-library/intro) for React component testing. + +It should be noted that in the past, React components were unit tested with [Enzyme](https://github.com/airbnb/enzyme). However, for new tests, it is preferred to use React Testing Library (RTL) and over time old tests should be refactored to use RTL too (typically when working on code that touches an old test). Assuming you've followed the [instructions](/docs/contributors/getting-started.md) to install Node and project dependencies, tests can be run from the command-line with NPM: @@ -459,8 +461,16 @@ Tests for PHP use [PHPUnit](https://phpunit.de/) as the testing framework. If yo npm run test-php ``` +To re-run tests automatically when files change (similar to Jest), run: + +``` +npm run test-php:watch +``` + _Note: The phpunit commands require `wp-env` to be running and composer dependencies to be installed. The package script will start wp-env for you if it is not already running._ +In other environments, run `composer run test` and `composer run test:watch`. + Code style in PHP is enforced using [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer). It is recommended that you install PHP_CodeSniffer and the [WordPress Coding Standards for PHP_CodeSniffer](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards#installation) ruleset using [Composer](https://getcomposer.org/). With Composer installed, run `composer install` from the project directory to install dependencies. The above `npm run test-php` will execute both unit tests and code linting. Code linting can be verified independently by running `npm run lint-php`. To run unit tests only, without the linter, use `npm run test-unit-php` instead. diff --git a/docs/contributors/triage.md b/docs/contributors/triage.md index bc6d6ad4b4f630..6da30fc5526c98 100644 --- a/docs/contributors/triage.md +++ b/docs/contributors/triage.md @@ -33,11 +33,6 @@ When triaging, either one of the lists above or issues in general, work through * First search for duplicates. If the issue is duplicate, close it by commenting with “Duplicate of #” and add any relevant new details to the existing issue. (Don’t forget to search for duplicates among closed issues as well!). * If the issue is missing labels, add some to better categorize it (requires proper permissions given after joining the triage team). A good starting place when adding labels is to apply one of the labels prefixed [Type] (e.g. [Type] Enhancement or [Type] Bug) to indicate what kind of issue it is. After that consider adding more descriptive labels. If the issue concerns a particular core block, add one of the labels prefixed [Block]. Or if the issue affects a particular feature there are [Feature] labels. Finally, there are labels that affect particular interest areas, like Accessibility and Internationalization. You can view all possible labels [here](https://github.com/WordPress/gutenberg/labels). -* Consider adding priority if you can confidently determine what the likely level should be: - * Priority OMGWTFBBQ: Major issues that are causing failures and are reported frequently. Typically, these are issues that are critical because they break important behaviour or functionality. An example might be, “Unable to remove a block after it is added to the editor”. - * Priority: High: Fits one of the current focuses and is causing a major broken experience (including flow, visual bugs and blocks). - * Priority: Low: Enhancements that aren’t part of focuses, iche bugs, problems with old browsers. - * Note that it’s on purpose that no priority label infers a normal level. * If the title doesn’t communicate the issue clearly enough, edit it for clarity (requires proper permissions). Specifically, we’d recommend having the main feature the issue relates to in the beginning of the title ([example](https://github.com/WordPress/gutenberg/issues/6193)) and for the title to generally be as succinct yet descriptive as possible ([example](https://github.com/WordPress/gutenberg/issues/6193)). * If it’s a bug report, test to confirm the report or add the Needs Testing label. If there is not enough information to confirm the report, add the [Status] Needs More Info label and ask for the details needed. It’s particularly beneficial when a bug report has steps for reproduction so ask the reporter to add those if they’re missing. * Remove the [Status] Needs More Info if the author of the issue has responded with enough details. @@ -47,12 +42,40 @@ When triaging, either one of the lists above or issues in general, work through * Check that the bug report is valid by debugging it to see if you can track down the technical specifics. * Check if the issue is missing some detail and see if you can fill in those details. For instance, if a bug report is missing visual detail, it’s helpful to reproduce the issue locally and upload a screenshot or GIF. * Consider adding the Good First Issue label if you believe this is a relatively easy issue for a first-time contributor to try to solve. + +**Commonly Used Labels** -Generally speaking, the following labels are very useful for triaging issues and will likely be the ones you use the most consistently: -* Needs Technical Feedback - when you see new features or API changes proposed. -* Needs More Info - when it’s not clear what the issue is or it would help to provide additional details. -* Needs Testing - when old bugs seem like they are no longer relevant. -* [Type] Help Request - when someone is asking for general help with setup/implementation. +Generally speaking, the following labels are very useful for triaging issues and will likely be the ones you use the most consistently. You can view all possible labels [here](https://github.com/WordPress/gutenberg/labels). + +| Label | Reason | +| ------------- | ------------- | +|`[Type] Bug` | When an intended feature is broken. | +|`[Type] Enhancement` | When someone is suggesting an enhancement to a current feature. | +| `[Type] Help Request` | When someone is asking for general help with setup/implementation. | +| `Needs Technical Feedback` | When you see new features or API changes proposed. | +| `Needs More Info` | When it’s not clear what the issue is or it would help to provide additional details. | +| `Needs Testing` | When a new issue needs to be confirmed or old bugs seem like they are no longer relevant. | + +**Determining Priority Labels** + +If you have enough knowledge about the report at hand and feel confident in doing so, you can consider adding priority. Note that it’s on purpose that no priority label infers a normal level. + +| Label | Reason | +| ------------- | ------------- | +|`Priority OMGWTFBBQ` | Major issues that are causing failures and are reported frequently. Typically, these are issues that are critical because they break important behavior or functionality. An example might be, “Unable to remove a block after it is added to the editor”. | +|`Priority: High` | Fits one of the current focuses and is causing a major broken experience (including flow, visual bugs and blocks). | +| `Priority: Low` | Enhancements that aren’t part of focuses, niche bugs, problems with old browsers. | + +### Closing issues + +Issues are closed for the following reasons: + +* A PR and/or latest release resolved the reported issue. +* Duplicate of a current report. +* Help request that is best handled in the WordPress.org forums. +* An issue that's not able to be replicated. +* An issue that needs more information that the author of the issue hasn't responded to for 2+ weeks. +* An item that is determined as unable to be fixed or is working as intended. ### Release specific triage Here are some guidelines to follow when doing triage specifically around the time of a release. This is important to differentiate compared to general triage so problematic, release blocking bugs are properly identified and solutions are found. @@ -65,7 +88,7 @@ Here are some guidelines to follow when doing triage specifically around the tim Along with the general triage flows listed previously, there are some specific additions to the flows for more design-centric triage for design minded folks participating in triage. * PR testing and reviews: this should be your first stop for daily self triage. -* Needs design feedback: check if the issue does need design feedback and, if possible, give it. You can organise this by priority, project boards or by least commented. Once there are enough opinions, please remove this label and decide on next steps (ie adding the Needs Design label). +* Needs design feedback: check if the issue does need design feedback and, if possible, give it. You can organize this by priority, project boards or by least commented. Once there are enough opinions, please remove this label and decide on next steps (ie adding the Needs Design label). * Needs design: Does it really need a design? Does this fit a focus? If it has a design mark as ‘needs design feedback’ to better categorize the issue. Reminders: diff --git a/docs/contributors/versions-in-wordpress.md b/docs/contributors/versions-in-wordpress.md index 177537f7e2e2bb..90069db6af9794 100644 --- a/docs/contributors/versions-in-wordpress.md +++ b/docs/contributors/versions-in-wordpress.md @@ -6,6 +6,9 @@ If anything looks incorrect here, please bring it up in #core-editor in [WordPre | Gutenberg Versions | WordPress Version | | ------------------ | ----------------- | +| 8.6-9.2 | 5.6 | +| 7.6-8.5 | 5.5.3 | +| 7.6-8.5 | 5.5.2 | | 7.6-8.5 | 5.5.1 | | 7.6-8.5 | 5.5 | | 6.6-7.5 | 5.4.2 | diff --git a/docs/designers-developers/designers/animation.md b/docs/designers-developers/designers/animation.md index c0575e76eea823..ea861c8bb1a22d 100644 --- a/docs/designers-developers/designers/animation.md +++ b/docs/designers-developers/designers/animation.md @@ -30,7 +30,7 @@ Reuse animations if one already exists for your task. ## Accessibility Considerations -- Animations should be subtle. Be cognizent of users with [vestibular disorders triggered by motion](https://www.ncbi.nlm.nih.gov/pubmed/29017000). +- Animations should be subtle. Be cognizant of users with [vestibular disorders triggered by motion](https://www.ncbi.nlm.nih.gov/pubmed/29017000). - Don't animate elements that are currently reporting content to adaptive technology (e.g., an `aria-live` region that's receiving updates). This can cause confusion wherein the technology tries to parse a region that's actively changing. - Avoid animations that aren't directly triggered by user behaviors. - Whenever possible, ensure that animations respect the OS-level "Reduce Motion" settings. This can be done by utilizing the [`prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) media query. Gutenberg includes a `@reduce-motion` mixin for this, to be used alongside rules that include a CSS `animate` property. diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index da93e0733d409d..64fcab1c424a0a 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -2,280 +2,285 @@ For features included in the Gutenberg plugin, the deprecation policy is intended to support backward compatibility for two minor plugin releases, when possible. Features and code included in a stable release of WordPress are not included in this deprecation timeline, and are instead subject to the [versioning policies of the WordPress project](https://make.wordpress.org/core/handbook/about/release-cycle/version-numbering/). The current deprecations are listed below and are grouped by _the version at which they will be removed completely_. If your plugin depends on these behaviors, you must update to the recommended alternative before the noted version. +## 9.7.0 + +- `leftSidebar` prop in `InterfaceSkeleton` component has been removed. Use `secondarySidebar` prop instead. + ## 8.6.0 -- Block API integration with [Block Context](https://github.com/WordPress/gutenberg/blob/master/docs/designers-developers/developers/block-api/block-context.md) was updated. When registering a block use `usesContext` and `providesContext` pair in JavaScript files and `uses_context` and `provides_context` pair in PHP files instead of previous pair `context` and `providesContext`. +- Block API integration with [Block Context](https://github.com/WordPress/gutenberg/blob/master/docs/designers-developers/developers/block-api/block-context.md) was updated. When registering a block use `usesContext` and `providesContext` pair in JavaScript files and `uses_context` and `provides_context` pair in PHP files instead of previous pair `context` and `providesContext`. ## 8.3.0 -- The PHP function `gutenberg_get_post_from_context` has been removed. Use [Block Context](https://github.com/WordPress/gutenberg/blob/master/docs/designers-developers/developers/block-api/block-context.md) instead. -- The old Block Pattern APIs `register_pattern`/`unregister_pattern` have been removed. Use the [new functions](https://github.com/WordPress/gutenberg/blob/master/docs/designers-developers/developers/block-api/block-patterns.md#register_block_pattern) instead. +- The PHP function `gutenberg_get_post_from_context` has been removed. Use [Block Context](https://github.com/WordPress/gutenberg/blob/master/docs/designers-developers/developers/block-api/block-context.md) instead. +- The old Block Pattern APIs `register_pattern`/`unregister_pattern` have been removed. Use the [new functions](https://github.com/WordPress/gutenberg/blob/master/docs/designers-developers/developers/block-api/block-patterns.md#register_block_pattern) instead. ## 5.5.0 -- The PHP function `gutenberg_init` has been removed. -- The PHP function `is_gutenberg_page` has been removed. Use [`WP_Screen::is_block_editor`](https://developer.wordpress.org/reference/classes/wp_screen/is_block_editor/) instead. -- The PHP function `the_gutenberg_project` has been removed. -- The PHP function `gutenberg_default_post_format_template` has been removed. -- The PHP function `gutenberg_get_available_image_sizes` has been removed. -- The PHP function `gutenberg_get_autosave_newer_than_post_save` has been removed. -- The PHP function `gutenberg_editor_scripts_and_styles` has been removed. +- The PHP function `gutenberg_init` has been removed. +- The PHP function `is_gutenberg_page` has been removed. Use [`WP_Screen::is_block_editor`](https://developer.wordpress.org/reference/classes/wp_screen/is_block_editor/) instead. +- The PHP function `the_gutenberg_project` has been removed. +- The PHP function `gutenberg_default_post_format_template` has been removed. +- The PHP function `gutenberg_get_available_image_sizes` has been removed. +- The PHP function `gutenberg_get_autosave_newer_than_post_save` has been removed. +- The PHP function `gutenberg_editor_scripts_and_styles` has been removed. ## 5.4.0 -- The PHP function `gutenberg_load_plugin_textdomain` has been removed. -- The PHP function `gutenberg_get_jed_locale_data` has been removed. -- The PHP function `gutenberg_load_locale_data` has been removed. +- The PHP function `gutenberg_load_plugin_textdomain` has been removed. +- The PHP function `gutenberg_get_jed_locale_data` has been removed. +- The PHP function `gutenberg_load_locale_data` has been removed. ## 5.3.0 -- The PHP function `gutenberg_redirect_to_classic_editor_when_saving_posts` has been removed. -- The PHP function `gutenberg_revisions_link_to_editor` has been removed. -- The PHP function `gutenberg_remember_classic_editor_when_saving_posts` has been removed. -- The PHP function `gutenberg_can_edit_post_type` has been removed. Use [`use_block_editor_for_post_type`](https://developer.wordpress.org/reference/functions/use_block_editor_for_post_type/) instead. -- The PHP function `gutenberg_can_edit_post` has been removed. Use [`use_block_editor_for_post`](https://developer.wordpress.org/reference/functions/use_block_editor_for_post/) instead. +- The PHP function `gutenberg_redirect_to_classic_editor_when_saving_posts` has been removed. +- The PHP function `gutenberg_revisions_link_to_editor` has been removed. +- The PHP function `gutenberg_remember_classic_editor_when_saving_posts` has been removed. +- The PHP function `gutenberg_can_edit_post_type` has been removed. Use [`use_block_editor_for_post_type`](https://developer.wordpress.org/reference/functions/use_block_editor_for_post_type/) instead. +- The PHP function `gutenberg_can_edit_post` has been removed. Use [`use_block_editor_for_post`](https://developer.wordpress.org/reference/functions/use_block_editor_for_post/) instead. ## 5.2.0 -- The PHP function `gutenberg_parse_blocks` has been removed. Use [`parse_blocks`](https://developer.wordpress.org/reference/functions/parse_blocks/) instead. -- The PHP function `get_dynamic_blocks_regex` has been removed. -- The PHP function `gutenberg_render_block` has been removed. Use [`render_block`](https://developer.wordpress.org/reference/functions/render_block/) instead. -- The PHP function `strip_dynamic_blocks` has been removed. For use in excerpt preparation, consider [`excerpt_remove_blocks`](https://developer.wordpress.org/reference/functions/excerpt_remove_blocks/) instead. -- The PHP function `strip_dynamic_blocks_add_filter` has been removed. -- The PHP function `strip_dynamic_blocks_remove_filter` has been removed. -- The PHP function `gutenberg_post_has_blocks` has been removed. Use [`has_blocks`](https://developer.wordpress.org/reference/functions/has_blocks/) instead. -- The PHP function `gutenberg_content_has_blocks` has been removed. Use [`has_blocks`](https://developer.wordpress.org/reference/functions/has_blocks/) instead. -- The PHP function `gutenberg_register_rest_routes` has been removed. -- The PHP function `gutenberg_add_taxonomy_visibility_field` has been removed. -- The PHP function `gutenberg_get_taxonomy_visibility_data` has been removed. -- The PHP function `gutenberg_add_permalink_template_to_posts` has been removed. -- The PHP function `gutenberg_add_block_format_to_post_content` has been removed. -- The PHP function `gutenberg_add_target_schema_to_links` has been removed. -- The PHP function `gutenberg_register_post_prepare_functions` has been removed. -- The PHP function `gutenberg_silence_rest_errors` has been removed. -- The PHP function `gutenberg_filter_post_type_labels` has been removed. -- The PHP function `gutenberg_preload_api_request` has been removed. Use [`rest_preload_api_request`](https://developer.wordpress.org/reference/functions/rest_preload_api_request/) instead. -- The PHP function `gutenberg_remove_wpcom_markdown_support` has been removed. -- The PHP function `gutenberg_add_gutenberg_post_state` has been removed. -- The PHP function `gutenberg_bulk_post_updated_messages` has been removed. -- The PHP function `gutenberg_kses_allowedtags` has been removed. -- The PHP function `gutenberg_add_responsive_body_class` has been removed. -- The PHP function `gutenberg_add_edit_link_filters` has been removed. -- The PHP function `gutenberg_add_edit_link` has been removed. -- The PHP function `gutenberg_block_bulk_actions` has been removed. -- The PHP function `gutenberg_replace_default_add_new_button` has been removed. -- The PHP function `gutenberg_content_block_version` has been removed. Use [`block_version`](https://developer.wordpress.org/reference/functions/block_version/) instead. -- The PHP function `gutenberg_get_block_categories` has been removed. Use [`get_block_categories`](https://developer.wordpress.org/reference/functions/get_block_categories/) instead. -- The PHP function `register_tinymce_scripts` has been removed. Use [`wp_register_tinymce_scripts`](https://developer.wordpress.org/reference/functions/wp_register_tinymce_scripts/) instead. -- The PHP function `gutenberg_register_post_types` has been removed. -- The `gutenberg` theme support option has been removed. Use [`align-wide`](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#wide-alignment) instead. -- The PHP function `gutenberg_prepare_blocks_for_js` has been removed. Use [`get_block_editor_server_block_settings`](https://developer.wordpress.org/reference/functions/get_block_editor_server_block_settings/) instead. -- The PHP function `gutenberg_load_list_reusable_blocks` has been removed. -- The PHP function `_gutenberg_utf8_split` has been removed. Use `_mb_substr` instead. -- The PHP function `gutenberg_disable_editor_settings_wpautop` has been removed. -- The PHP function `gutenberg_add_rest_nonce_to_heartbeat_response_headers` has been removed. -- The PHP function `gutenberg_check_if_classic_needs_warning_about_blocks` has been removed. -- The PHP function `gutenberg_warn_classic_about_blocks` has been removed. -- The PHP function `gutenberg_show_privacy_policy_help_text` has been removed. -- The PHP function `gutenberg_common_scripts_and_styles` has been removed. Use [`wp_common_block_scripts_and_styles`](https://developer.wordpress.org/reference/functions/wp_common_block_scripts_and_styles/) instead. -- The PHP function `gutenberg_enqueue_registered_block_scripts_and_styles` has been removed. Use [`wp_enqueue_registered_block_scripts_and_styles`](https://developer.wordpress.org/reference/functions/wp_enqueue_registered_block_scripts_and_styles/) instead. -- The PHP function `gutenberg_meta_box_save` has been removed. -- The PHP function `gutenberg_meta_box_save_redirect` has been removed. -- The PHP function `gutenberg_filter_meta_boxes` has been removed. -- The PHP function `gutenberg_intercept_meta_box_render` has been removed. -- The PHP function `gutenberg_override_meta_box_callback` has been removed. -- The PHP function `gutenberg_show_meta_box_warning` has been removed. -- The PHP function `the_gutenberg_metaboxes` has been removed. Use [`the_block_editor_meta_boxes`](https://developer.wordpress.org/reference/functions/the_block_editor_meta_boxes/) instead. -- The PHP function `gutenberg_meta_box_post_form_hidden_fields` has been removed. Use [`the_block_editor_meta_box_post_form_hidden_fields`](https://developer.wordpress.org/reference/functions/the_block_editor_meta_box_post_form_hidden_fields/) instead. -- The PHP function `gutenberg_toggle_custom_fields` has been removed. -- The PHP function `gutenberg_collect_meta_box_data` has been removed. Use [`register_and_do_post_meta_boxes`](https://developer.wordpress.org/reference/functions/register_and_do_post_meta_boxes/) instead. -- `window._wpLoadGutenbergEditor` has been removed. Use `window._wpLoadBlockEditor` instead. Note: This is a private API, not intended for public use. It may be removed in the future. -- The PHP function `gutenberg_get_script_polyfill` has been removed. Use [`wp_get_script_polyfill`](https://developer.wordpress.org/reference/functions/wp_get_script_polyfill/) instead. -- The PHP function `gutenberg_add_admin_body_class` has been removed. Use the `.block-editor-page` class selector in your stylesheets if you need to scope styles to the block editor screen. +- The PHP function `gutenberg_parse_blocks` has been removed. Use [`parse_blocks`](https://developer.wordpress.org/reference/functions/parse_blocks/) instead. +- The PHP function `get_dynamic_blocks_regex` has been removed. +- The PHP function `gutenberg_render_block` has been removed. Use [`render_block`](https://developer.wordpress.org/reference/functions/render_block/) instead. +- The PHP function `strip_dynamic_blocks` has been removed. For use in excerpt preparation, consider [`excerpt_remove_blocks`](https://developer.wordpress.org/reference/functions/excerpt_remove_blocks/) instead. +- The PHP function `strip_dynamic_blocks_add_filter` has been removed. +- The PHP function `strip_dynamic_blocks_remove_filter` has been removed. +- The PHP function `gutenberg_post_has_blocks` has been removed. Use [`has_blocks`](https://developer.wordpress.org/reference/functions/has_blocks/) instead. +- The PHP function `gutenberg_content_has_blocks` has been removed. Use [`has_blocks`](https://developer.wordpress.org/reference/functions/has_blocks/) instead. +- The PHP function `gutenberg_register_rest_routes` has been removed. +- The PHP function `gutenberg_add_taxonomy_visibility_field` has been removed. +- The PHP function `gutenberg_get_taxonomy_visibility_data` has been removed. +- The PHP function `gutenberg_add_permalink_template_to_posts` has been removed. +- The PHP function `gutenberg_add_block_format_to_post_content` has been removed. +- The PHP function `gutenberg_add_target_schema_to_links` has been removed. +- The PHP function `gutenberg_register_post_prepare_functions` has been removed. +- The PHP function `gutenberg_silence_rest_errors` has been removed. +- The PHP function `gutenberg_filter_post_type_labels` has been removed. +- The PHP function `gutenberg_preload_api_request` has been removed. Use [`rest_preload_api_request`](https://developer.wordpress.org/reference/functions/rest_preload_api_request/) instead. +- The PHP function `gutenberg_remove_wpcom_markdown_support` has been removed. +- The PHP function `gutenberg_add_gutenberg_post_state` has been removed. +- The PHP function `gutenberg_bulk_post_updated_messages` has been removed. +- The PHP function `gutenberg_kses_allowedtags` has been removed. +- The PHP function `gutenberg_add_responsive_body_class` has been removed. +- The PHP function `gutenberg_add_edit_link_filters` has been removed. +- The PHP function `gutenberg_add_edit_link` has been removed. +- The PHP function `gutenberg_block_bulk_actions` has been removed. +- The PHP function `gutenberg_replace_default_add_new_button` has been removed. +- The PHP function `gutenberg_content_block_version` has been removed. Use [`block_version`](https://developer.wordpress.org/reference/functions/block_version/) instead. +- The PHP function `gutenberg_get_block_categories` has been removed. Use [`get_block_categories`](https://developer.wordpress.org/reference/functions/get_block_categories/) instead. +- The PHP function `register_tinymce_scripts` has been removed. Use [`wp_register_tinymce_scripts`](https://developer.wordpress.org/reference/functions/wp_register_tinymce_scripts/) instead. +- The PHP function `gutenberg_register_post_types` has been removed. +- The `gutenberg` theme support option has been removed. Use [`align-wide`](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#wide-alignment) instead. +- The PHP function `gutenberg_prepare_blocks_for_js` has been removed. Use [`get_block_editor_server_block_settings`](https://developer.wordpress.org/reference/functions/get_block_editor_server_block_settings/) instead. +- The PHP function `gutenberg_load_list_reusable_blocks` has been removed. +- The PHP function `_gutenberg_utf8_split` has been removed. Use `_mb_substr` instead. +- The PHP function `gutenberg_disable_editor_settings_wpautop` has been removed. +- The PHP function `gutenberg_add_rest_nonce_to_heartbeat_response_headers` has been removed. +- The PHP function `gutenberg_check_if_classic_needs_warning_about_blocks` has been removed. +- The PHP function `gutenberg_warn_classic_about_blocks` has been removed. +- The PHP function `gutenberg_show_privacy_policy_help_text` has been removed. +- The PHP function `gutenberg_common_scripts_and_styles` has been removed. Use [`wp_common_block_scripts_and_styles`](https://developer.wordpress.org/reference/functions/wp_common_block_scripts_and_styles/) instead. +- The PHP function `gutenberg_enqueue_registered_block_scripts_and_styles` has been removed. Use [`wp_enqueue_registered_block_scripts_and_styles`](https://developer.wordpress.org/reference/functions/wp_enqueue_registered_block_scripts_and_styles/) instead. +- The PHP function `gutenberg_meta_box_save` has been removed. +- The PHP function `gutenberg_meta_box_save_redirect` has been removed. +- The PHP function `gutenberg_filter_meta_boxes` has been removed. +- The PHP function `gutenberg_intercept_meta_box_render` has been removed. +- The PHP function `gutenberg_override_meta_box_callback` has been removed. +- The PHP function `gutenberg_show_meta_box_warning` has been removed. +- The PHP function `the_gutenberg_metaboxes` has been removed. Use [`the_block_editor_meta_boxes`](https://developer.wordpress.org/reference/functions/the_block_editor_meta_boxes/) instead. +- The PHP function `gutenberg_meta_box_post_form_hidden_fields` has been removed. Use [`the_block_editor_meta_box_post_form_hidden_fields`](https://developer.wordpress.org/reference/functions/the_block_editor_meta_box_post_form_hidden_fields/) instead. +- The PHP function `gutenberg_toggle_custom_fields` has been removed. +- The PHP function `gutenberg_collect_meta_box_data` has been removed. Use [`register_and_do_post_meta_boxes`](https://developer.wordpress.org/reference/functions/register_and_do_post_meta_boxes/) instead. +- `window._wpLoadGutenbergEditor` has been removed. Use `window._wpLoadBlockEditor` instead. Note: This is a private API, not intended for public use. It may be removed in the future. +- The PHP function `gutenberg_get_script_polyfill` has been removed. Use [`wp_get_script_polyfill`](https://developer.wordpress.org/reference/functions/wp_get_script_polyfill/) instead. +- The PHP function `gutenberg_add_admin_body_class` has been removed. Use the `.block-editor-page` class selector in your stylesheets if you need to scope styles to the block editor screen. ## 4.5.0 -- `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. -- `wp.editor.PostPublishPanelToggle` has been deprecated in favor of `wp.editor.PostPublishButton`. + +- `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. +- `wp.editor.PostPublishPanelToggle` has been deprecated in favor of `wp.editor.PostPublishButton`. ## 4.4.0 -- `wp.date.getSettings` has been removed. Please use `wp.date.__experimentalGetSettings` instead. -- `wp.compose.remountOnPropChange` has been removed. -- The following editor store actions have been removed: `createNotice`, `removeNotice`, `createSuccessNotice`, `createInfoNotice`, `createErrorNotice`, `createWarningNotice`. Use the equivalent actions by the same name from the `@wordpress/notices` module. -- The id prop of wp.nux.DotTip has been removed. Please use the tipId prop instead. -- `wp.blocks.isValidBlock` has been removed. Please use `wp.blocks.isValidBlockContent` instead but keep in mind that the order of params has changed. -- `wp.data` `registry.registerReducer` has been deprecated. Use `registry.registerStore` instead. -- `wp.data` `registry.registerSelectors` has been deprecated. Use `registry.registerStore` instead. -- `wp.data` `registry.registerActions` has been deprecated. Use `registry.registerStore` instead. -- `wp.data` `registry.registerResolvers` has been deprecated. Use `registry.registerStore` instead. -- `moment` has been removed from the public API for the date module. +- `wp.date.getSettings` has been removed. Please use `wp.date.__experimentalGetSettings` instead. +- `wp.compose.remountOnPropChange` has been removed. +- The following editor store actions have been removed: `createNotice`, `removeNotice`, `createSuccessNotice`, `createInfoNotice`, `createErrorNotice`, `createWarningNotice`. Use the equivalent actions by the same name from the `@wordpress/notices` module. +- The id prop of wp.nux.DotTip has been removed. Please use the tipId prop instead. +- `wp.blocks.isValidBlock` has been removed. Please use `wp.blocks.isValidBlockContent` instead but keep in mind that the order of params has changed. +- `wp.data` `registry.registerReducer` has been deprecated. Use `registry.registerStore` instead. +- `wp.data` `registry.registerSelectors` has been deprecated. Use `registry.registerStore` instead. +- `wp.data` `registry.registerActions` has been deprecated. Use `registry.registerStore` instead. +- `wp.data` `registry.registerResolvers` has been deprecated. Use `registry.registerStore` instead. +- `moment` has been removed from the public API for the date module. ## 4.3.0 -- `isEditorSidebarPanelOpened` selector (`core/edit-post`) has been removed. Please use `isEditorPanelEnabled` instead. -- `toggleGeneralSidebarEditorPanel` action (`core/edit-post`) has been removed. Please use `toggleEditorPanelOpened` instead. -- `wp.components.PanelColor` component has been removed. Please use `wp.editor.PanelColorSettings` instead. -- `wp.editor.PanelColor` component has been removed. Please use `wp.editor.PanelColorSettings` instead. +- `isEditorSidebarPanelOpened` selector (`core/edit-post`) has been removed. Please use `isEditorPanelEnabled` instead. +- `toggleGeneralSidebarEditorPanel` action (`core/edit-post`) has been removed. Please use `toggleEditorPanelOpened` instead. +- `wp.components.PanelColor` component has been removed. Please use `wp.editor.PanelColorSettings` instead. +- `wp.editor.PanelColor` component has been removed. Please use `wp.editor.PanelColorSettings` instead. ## 4.2.0 -- Writing resolvers as async generators has been removed. Use the controls plugin instead. -- `wp.components.AccessibleSVG` component has been removed. Please use `wp.components.SVG` instead. -- The `wp.editor.UnsavedChangesWarning` component no longer accepts a `forceIsDirty` prop. -- `setActiveMetaBoxLocations` action (`core/edit-post`) has been removed. -- `initializeMetaBoxState` action (`core/edit-post`) has been removed. -- `wp.editPost.initializeEditor` no longer returns an object. Use the `setActiveMetaBoxLocations` action (`core/edit-post`) in place of the existing object's `initializeMetaBoxes` function. -- `setMetaBoxSavedData` action (`core/edit-post`) has been removed. -- `getMetaBoxes` selector (`core/edit-post`) has been removed. Use `getActiveMetaBoxLocations` selector (`core/edit-post`) instead. -- `getMetaBox` selector (`core/edit-post`) has been removed. Use `isMetaBoxLocationActive` selector (`core/edit-post`) instead. -- Attribute type coercion has been removed. Omit the source to preserve type via serialized comment demarcation. -- `mediaDetails` in object passed to `onFileChange` callback of `wp.editor.mediaUpload`. Please use `media_details` property instead. -- `wp.components.CodeEditor` has been removed. Used `wp.codeEditor` directly instead. -- `wp.blocks.setUnknownTypeHandlerName` has been removed. Please use `setFreeformContentHandlerName` and `setUnregisteredTypeHandlerName` instead. -- `wp.blocks.getUnknownTypeHandlerName` has been removed. Please use `getFreeformContentHandlerName` and `getUnregisteredTypeHandlerName` instead. -- The Reusable blocks Data API was marked as experimental as it's subject to change in the future. +- Writing resolvers as async generators has been removed. Use the controls plugin instead. +- `wp.components.AccessibleSVG` component has been removed. Please use `wp.components.SVG` instead. +- The `wp.editor.UnsavedChangesWarning` component no longer accepts a `forceIsDirty` prop. +- `setActiveMetaBoxLocations` action (`core/edit-post`) has been removed. +- `initializeMetaBoxState` action (`core/edit-post`) has been removed. +- `wp.editPost.initializeEditor` no longer returns an object. Use the `setActiveMetaBoxLocations` action (`core/edit-post`) in place of the existing object's `initializeMetaBoxes` function. +- `setMetaBoxSavedData` action (`core/edit-post`) has been removed. +- `getMetaBoxes` selector (`core/edit-post`) has been removed. Use `getActiveMetaBoxLocations` selector (`core/edit-post`) instead. +- `getMetaBox` selector (`core/edit-post`) has been removed. Use `isMetaBoxLocationActive` selector (`core/edit-post`) instead. +- Attribute type coercion has been removed. Omit the source to preserve type via serialized comment demarcation. +- `mediaDetails` in object passed to `onFileChange` callback of `wp.editor.mediaUpload`. Please use `media_details` property instead. +- `wp.components.CodeEditor` has been removed. Used `wp.codeEditor` directly instead. +- `wp.blocks.setUnknownTypeHandlerName` has been removed. Please use `setFreeformContentHandlerName` and `setUnregisteredTypeHandlerName` instead. +- `wp.blocks.getUnknownTypeHandlerName` has been removed. Please use `getFreeformContentHandlerName` and `getUnregisteredTypeHandlerName` instead. +- The Reusable blocks Data API was marked as experimental as it's subject to change in the future. ## 4.1.0 -- `wp.data.dispatch( 'core/editor' ).checkTemplateValidity` has been removed. Validity is verified automatically upon block reset. +- `wp.data.dispatch( 'core/editor' ).checkTemplateValidity` has been removed. Validity is verified automatically upon block reset. ## 4.0.0 -- `wp.editor.RichTextProvider` has been removed. Please use `wp.data.select( 'core/editor' )` methods instead. -- `wp.components.Draggable` as a DOM node drag handler has been removed. Please, use `wp.components.Draggable` as a wrap component for your DOM node drag handler. -- `wp.i18n.getI18n` has been removed. Use `__`, `_x`, `_n`, or `_nx` instead. -- `wp.i18n.dcnpgettext` has been removed. Use `__`, `_x`, `_n`, or `_nx` instead. +- `wp.editor.RichTextProvider` has been removed. Please use `wp.data.select( 'core/editor' )` methods instead. +- `wp.components.Draggable` as a DOM node drag handler has been removed. Please, use `wp.components.Draggable` as a wrap component for your DOM node drag handler. +- `wp.i18n.getI18n` has been removed. Use `__`, `_x`, `_n`, or `_nx` instead. +- `wp.i18n.dcnpgettext` has been removed. Use `__`, `_x`, `_n`, or `_nx` instead. ## 3.9.0 -- RichText `getSettings` prop has been removed. The `unstableGetSettings` prop is available if continued use is required. Unstable APIs are strongly discouraged to be used, and are subject to removal without notice. -- RichText `onSetup` prop has been removed. The `unstableOnSetup` prop is available if continued use is required. Unstable APIs are strongly discouraged to be used, and are subject to removal without notice. -- `wp.editor.getColorName` has been removed. Please use `wp.editor.getColorObjectByColorValue` instead. -- `wp.editor.getColorClass` has been renamed. Please use `wp.editor.getColorClassName` instead. -- `value` property in color objects passed by `wp.editor.withColors` has been removed. Please use color property instead. -- The Subheading block has been removed. Please use the Paragraph block instead. -- `wp.blocks.getDefaultBlockForPostFormat` has been removed. +- RichText `getSettings` prop has been removed. The `unstableGetSettings` prop is available if continued use is required. Unstable APIs are strongly discouraged to be used, and are subject to removal without notice. +- RichText `onSetup` prop has been removed. The `unstableOnSetup` prop is available if continued use is required. Unstable APIs are strongly discouraged to be used, and are subject to removal without notice. +- `wp.editor.getColorName` has been removed. Please use `wp.editor.getColorObjectByColorValue` instead. +- `wp.editor.getColorClass` has been renamed. Please use `wp.editor.getColorClassName` instead. +- `value` property in color objects passed by `wp.editor.withColors` has been removed. Please use color property instead. +- The Subheading block has been removed. Please use the Paragraph block instead. +- `wp.blocks.getDefaultBlockForPostFormat` has been removed. ## 3.8.0 - - `wp.components.withContext` has been removed. Please use `wp.element.createContext` instead. See: https://reactjs.org/docs/context.html. - - `wp.coreBlocks.registerCoreBlocks` has been removed. Please use `wp.blockLibrary.registerCoreBlocks` instead. - - `wp.editor.DocumentTitle` component has been removed. - - `getDocumentTitle` selector (`core/editor`) has been removed. +- `wp.components.withContext` has been removed. Please use `wp.element.createContext` instead. See: https://reactjs.org/docs/context.html. +- `wp.coreBlocks.registerCoreBlocks` has been removed. Please use `wp.blockLibrary.registerCoreBlocks` instead. +- `wp.editor.DocumentTitle` component has been removed. +- `getDocumentTitle` selector (`core/editor`) has been removed. ## 3.7.0 - - `wp.components.withAPIData` has been removed. Please use the Core Data module or `wp.apiFetch` directly instead. - - `wp.data.dispatch("core").receiveTerms` has been deprecated. Please use `wp.data.dispatch("core").receiveEntityRecords` instead. - - `getCategories` resolver has been deprecated. Please use `getEntityRecords` resolver instead. - - `wp.data.select("core").getTerms` has been deprecated. Please use `wp.data.select("core").getEntityRecords` instead. - - `wp.data.select("core").getCategories` has been deprecated. Please use `wp.data.select("core").getEntityRecords` instead. - - `wp.data.select("core").isRequestingCategories` has been deprecated. Please use `wp.data.select("core/data").isResolving` instead. - - `wp.data.select("core").isRequestingTerms` has been deprecated. Please use `wp.data.select("core").isResolving` instead. - - `wp.data.restrictPersistence`, `wp.data.setPersistenceStorage` and `wp.data.setupPersistence` has been removed. Please use the data persistence plugin instead. +- `wp.components.withAPIData` has been removed. Please use the Core Data module or `wp.apiFetch` directly instead. +- `wp.data.dispatch("core").receiveTerms` has been deprecated. Please use `wp.data.dispatch("core").receiveEntityRecords` instead. +- `getCategories` resolver has been deprecated. Please use `getEntityRecords` resolver instead. +- `wp.data.select("core").getTerms` has been deprecated. Please use `wp.data.select("core").getEntityRecords` instead. +- `wp.data.select("core").getCategories` has been deprecated. Please use `wp.data.select("core").getEntityRecords` instead. +- `wp.data.select("core").isRequestingCategories` has been deprecated. Please use `wp.data.select("core/data").isResolving` instead. +- `wp.data.select("core").isRequestingTerms` has been deprecated. Please use `wp.data.select("core").isResolving` instead. +- `wp.data.restrictPersistence`, `wp.data.setPersistenceStorage` and `wp.data.setupPersistence` has been removed. Please use the data persistence plugin instead. ## 3.6.0 - - `wp.editor.editorMediaUpload` has been removed. Please use `wp.editor.mediaUpload` instead. - - `wp.utils.getMimeTypesArray` has been removed. - - `wp.utils.mediaUpload` has been removed. Please use `wp.editor.mediaUpload` instead. - - `wp.utils.preloadImage` has been removed. - - `supports.wideAlign` has been removed from the Block API. Please use `supports.alignWide` instead. - - `wp.blocks.isSharedBlock` has been removed. Use `wp.blocks.isReusableBlock` instead. - - `fetchSharedBlocks` action (`core/editor`) has been removed. Use `fetchReusableBlocks` instead. - - `receiveSharedBlocks` action (`core/editor`) has been removed. Use `receiveReusableBlocks` instead. - - `saveSharedBlock` action (`core/editor`) has been removed. Use `saveReusableBlock` instead. - - `deleteSharedBlock` action (`core/editor`) has been removed. Use `deleteReusableBlock` instead. - - `updateSharedBlockTitle` action (`core/editor`) has been removed. Use `updateReusableBlockTitle` instead. - - `convertBlockToSaved` action (`core/editor`) has been removed. Use `convertBlockToReusable` instead. - - `getSharedBlock` selector (`core/editor`) has been removed. Use `getReusableBlock` instead. - - `isSavingSharedBlock` selector (`core/editor`) has been removed. Use `isSavingReusableBlock` instead. - - `isFetchingSharedBlock` selector (`core/editor`) has been removed. Use `isFetchingReusableBlock` instead. - - `getSharedBlocks` selector (`core/editor`) has been removed. Use `getReusableBlocks` instead. +- `wp.editor.editorMediaUpload` has been removed. Please use `wp.editor.mediaUpload` instead. +- `wp.utils.getMimeTypesArray` has been removed. +- `wp.utils.mediaUpload` has been removed. Please use `wp.editor.mediaUpload` instead. +- `wp.utils.preloadImage` has been removed. +- `supports.wideAlign` has been removed from the Block API. Please use `supports.alignWide` instead. +- `wp.blocks.isSharedBlock` has been removed. Use `wp.blocks.isReusableBlock` instead. +- `fetchSharedBlocks` action (`core/editor`) has been removed. Use `fetchReusableBlocks` instead. +- `receiveSharedBlocks` action (`core/editor`) has been removed. Use `receiveReusableBlocks` instead. +- `saveSharedBlock` action (`core/editor`) has been removed. Use `saveReusableBlock` instead. +- `deleteSharedBlock` action (`core/editor`) has been removed. Use `deleteReusableBlock` instead. +- `updateSharedBlockTitle` action (`core/editor`) has been removed. Use `updateReusableBlockTitle` instead. +- `convertBlockToSaved` action (`core/editor`) has been removed. Use `convertBlockToReusable` instead. +- `getSharedBlock` selector (`core/editor`) has been removed. Use `getReusableBlock` instead. +- `isSavingSharedBlock` selector (`core/editor`) has been removed. Use `isSavingReusableBlock` instead. +- `isFetchingSharedBlock` selector (`core/editor`) has been removed. Use `isFetchingReusableBlock` instead. +- `getSharedBlocks` selector (`core/editor`) has been removed. Use `getReusableBlocks` instead. ## 3.5.0 - - `wp.components.ifCondition` has been removed. Please use `wp.compose.ifCondition` instead. - - `wp.components.withGlobalEvents` has been removed. Please use `wp.compose.withGlobalEvents` instead. - - `wp.components.withInstanceId` has been removed. Please use `wp.compose.withInstanceId` instead. - - `wp.components.withSafeTimeout` has been removed. Please use `wp.compose.withSafeTimeout` instead. - - `wp.components.withState` has been removed. Please use `wp.compose.withState` instead. - - `wp.element.pure` has been removed. Please use `wp.compose.pure` instead. - - `wp.element.compose` has been removed. Please use `wp.compose.compose` instead. - - `wp.element.createHigherOrderComponent` has been removed. Please use `wp.compose.createHigherOrderComponent` instead. - - `wp.utils.buildTermsTree` has been removed. - - `wp.utils.decodeEntities` has been removed. Please use `wp.htmlEntities.decodeEntities` instead. - - All references to a block's `uid` have been replaced with equivalent props and selectors for `clientId`. - - The `wp.editor.MediaPlaceholder` component `onSelectUrl` prop has been renamed to `onSelectURL`. - - The `wp.editor.UrlInput` component has been renamed to `wp.editor.URLInput`. - - The Text Columns block has been removed. Please use the Columns block instead. - - `InnerBlocks` grouped layout is removed. Use intermediary nested inner blocks instead. See Columns / Column block for reference implementation. - - `RichText` explicit `element` format removed. Please use the compatible `children` format instead. +- `wp.components.ifCondition` has been removed. Please use `wp.compose.ifCondition` instead. +- `wp.components.withGlobalEvents` has been removed. Please use `wp.compose.withGlobalEvents` instead. +- `wp.components.withInstanceId` has been removed. Please use `wp.compose.withInstanceId` instead. +- `wp.components.withSafeTimeout` has been removed. Please use `wp.compose.withSafeTimeout` instead. +- `wp.components.withState` has been removed. Please use `wp.compose.withState` instead. +- `wp.element.pure` has been removed. Please use `wp.compose.pure` instead. +- `wp.element.compose` has been removed. Please use `wp.compose.compose` instead. +- `wp.element.createHigherOrderComponent` has been removed. Please use `wp.compose.createHigherOrderComponent` instead. +- `wp.utils.buildTermsTree` has been removed. +- `wp.utils.decodeEntities` has been removed. Please use `wp.htmlEntities.decodeEntities` instead. +- All references to a block's `uid` have been replaced with equivalent props and selectors for `clientId`. +- The `wp.editor.MediaPlaceholder` component `onSelectUrl` prop has been renamed to `onSelectURL`. +- The `wp.editor.UrlInput` component has been renamed to `wp.editor.URLInput`. +- The Text Columns block has been removed. Please use the Columns block instead. +- `InnerBlocks` grouped layout is removed. Use intermediary nested inner blocks instead. See Columns / Column block for reference implementation. +- `RichText` explicit `element` format removed. Please use the compatible `children` format instead. ## 3.4.0 - - `focusOnMount` prop in the `Popover` component has been changed from `Boolean`-only to an enum-style property that accepts `"firstElement"`, `"container"`, or `false`. Please convert any `` usage to ``. - - `wp.utils.keycodes` utilities are removed. Please use `wp.keycodes` instead. - - Block `id` prop in `edit` function removed. Please use block `clientId` prop instead. - - `property` source removed. Please use equivalent `text`, `html`, or `attribute` source, or comment attribute instead. +- `focusOnMount` prop in the `Popover` component has been changed from `Boolean`-only to an enum-style property that accepts `"firstElement"`, `"container"`, or `false`. Please convert any `` usage to ``. +- `wp.utils.keycodes` utilities are removed. Please use `wp.keycodes` instead. +- Block `id` prop in `edit` function removed. Please use block `clientId` prop instead. +- `property` source removed. Please use equivalent `text`, `html`, or `attribute` source, or comment attribute instead. ## 3.3.0 - - `useOnce: true` has been removed from the Block API. Please use `supports.multiple: false` instead. - - Serializing components using `componentWillMount` lifecycle method. Please use the constructor instead. - - `blocks.Autocomplete.completers` filter removed. Please use `editor.Autocomplete.completers` instead. - - `blocks.BlockEdit` filter removed. Please use `editor.BlockEdit` instead. - - `blocks.BlockListBlock` filter removed. Please use `editor.BlockListBlock` instead. - - `blocks.MediaUpload` filter removed. Please use `editor.MediaUpload` instead. +- `useOnce: true` has been removed from the Block API. Please use `supports.multiple: false` instead. +- Serializing components using `componentWillMount` lifecycle method. Please use the constructor instead. +- `blocks.Autocomplete.completers` filter removed. Please use `editor.Autocomplete.completers` instead. +- `blocks.BlockEdit` filter removed. Please use `editor.BlockEdit` instead. +- `blocks.BlockListBlock` filter removed. Please use `editor.BlockListBlock` instead. +- `blocks.MediaUpload` filter removed. Please use `editor.MediaUpload` instead. ## 3.2.0 - - `wp.data.withRehydratation` has been renamed to `wp.data.withRehydration`. - - The `wp.editor.ImagePlaceholder` component is removed. Please use `wp.editor.MediaPlaceholder` instead. - - `wp.utils.deprecated` function removed. Please use `wp.deprecated` instead. - - `wp.utils.blob` removed. Please use `wp.blob` instead. - - `getInserterItems`: the `allowedBlockTypes` argument was removed and the `parentUID` argument was added. - - `getFrecentInserterItems` selector removed. Please use `getInserterItems` instead. - - `getSupportedBlocks` selector removed. Please use `canInsertBlockType` instead. +- `wp.data.withRehydratation` has been renamed to `wp.data.withRehydration`. +- The `wp.editor.ImagePlaceholder` component is removed. Please use `wp.editor.MediaPlaceholder` instead. +- `wp.utils.deprecated` function removed. Please use `wp.deprecated` instead. +- `wp.utils.blob` removed. Please use `wp.blob` instead. +- `getInserterItems`: the `allowedBlockTypes` argument was removed and the `parentUID` argument was added. +- `getFrecentInserterItems` selector removed. Please use `getInserterItems` instead. +- `getSupportedBlocks` selector removed. Please use `canInsertBlockType` instead. ## 3.1.0 - - All components in `wp.blocks.*` are removed. Please use `wp.editor.*` instead. - - `wp.blocks.withEditorSettings` is removed. Please use the data module to access the editor settings `wp.data.select( "core/editor" ).getEditorSettings()`. - - All DOM utils in `wp.utils.*` are removed. Please use `wp.dom.*` instead. - - `isPrivate: true` has been removed from the Block API. Please use `supports.inserter: false` instead. - - `wp.utils.isExtraSmall` function removed. Please use `wp.viewport` module instead. - - `getEditedPostExcerpt` selector removed (`core/editor`). Use `getEditedPostAttribute( 'excerpt' )` instead. +- All components in `wp.blocks.*` are removed. Please use `wp.editor.*` instead. +- `wp.blocks.withEditorSettings` is removed. Please use the data module to access the editor settings `wp.data.select( "core/editor" ).getEditorSettings()`. +- All DOM utils in `wp.utils.*` are removed. Please use `wp.dom.*` instead. +- `isPrivate: true` has been removed from the Block API. Please use `supports.inserter: false` instead. +- `wp.utils.isExtraSmall` function removed. Please use `wp.viewport` module instead. +- `getEditedPostExcerpt` selector removed (`core/editor`). Use `getEditedPostAttribute( 'excerpt' )` instead. ## 3.0.0 - - `wp.blocks.registerCoreBlocks` function removed. Please use `wp.coreBlocks.registerCoreBlocks` instead. - - Raw TinyMCE event handlers for `RichText` have been deprecated. Please use [documented props](https://github.com/WordPress/gutenberg/blob/v3.0.0/editor/components/rich-text/README.md), ancestor event handler, or onSetup access to the internal editor instance event hub instead. +- `wp.blocks.registerCoreBlocks` function removed. Please use `wp.coreBlocks.registerCoreBlocks` instead. +- Raw TinyMCE event handlers for `RichText` have been deprecated. Please use [documented props](https://github.com/WordPress/gutenberg/blob/v3.0.0/editor/components/rich-text/README.md), ancestor event handler, or onSetup access to the internal editor instance event hub instead. ## 2.8.0 - - `Original autocompleter interface in wp.components.Autocomplete` updated. Please use `latest autocompleter interface` instead. See [autocomplete](https://github.com/WordPress/gutenberg/blob/v2.8.0/components/autocomplete/README.md) for more info. - - `getInserterItems`: the `allowedBlockTypes` argument is now mandatory. - - `getFrecentInserterItems`: the `allowedBlockTypes` argument is now mandatory. +- `Original autocompleter interface in wp.components.Autocomplete` updated. Please use `latest autocompleter interface` instead. See [autocomplete](https://github.com/WordPress/gutenberg/blob/v2.8.0/components/autocomplete/README.md) for more info. +- `getInserterItems`: the `allowedBlockTypes` argument is now mandatory. +- `getFrecentInserterItems`: the `allowedBlockTypes` argument is now mandatory. ## 2.7.0 - - `wp.element.getWrapperDisplayName` function removed. Please use `wp.element.createHigherOrderComponent` instead. +- `wp.element.getWrapperDisplayName` function removed. Please use `wp.element.createHigherOrderComponent` instead. ## 2.6.0 - - `wp.blocks.getBlockDefaultClassname` function removed. Please use `wp.blocks.getBlockDefaultClassName` instead. - - `wp.blocks.Editable` component removed. Please use the `wp.blocks.RichText` component instead. +- `wp.blocks.getBlockDefaultClassname` function removed. Please use `wp.blocks.getBlockDefaultClassName` instead. +- `wp.blocks.Editable` component removed. Please use the `wp.blocks.RichText` component instead. ## 2.5.0 - - Returning raw HTML from block `save` is unsupported. Please use the `wp.element.RawHTML` component instead. - - `wp.data.query` higher-order component removed. Please use `wp.data.withSelect` instead. +- Returning raw HTML from block `save` is unsupported. Please use the `wp.element.RawHTML` component instead. +- `wp.data.query` higher-order component removed. Please use `wp.data.withSelect` instead. ## 2.4.0 - - `wp.blocks.BlockDescription` component removed. Please use the `description` block property instead. - - `wp.blocks.InspectorControls.*` components removed. Please use `wp.components.*` components instead. - - `wp.blocks.source.*` matchers removed. Please use the declarative attributes instead. See [block attributes](/docs/designers-developers/developers/block-api/block-attributes.md) for more info. - - `wp.data.select( 'selector', ...args )` removed. Please use `wp.data.select( reducerKey' ).*` instead. - - `wp.blocks.MediaUploadButton` component removed. Please use `wp.blocks.MediaUpload` component instead. +- `wp.blocks.BlockDescription` component removed. Please use the `description` block property instead. +- `wp.blocks.InspectorControls.*` components removed. Please use `wp.components.*` components instead. +- `wp.blocks.source.*` matchers removed. Please use the declarative attributes instead. See [block attributes](/docs/designers-developers/developers/block-api/block-attributes.md) for more info. +- `wp.data.select( 'selector', ...args )` removed. Please use `wp.data.select( reducerKey' ).*` instead. +- `wp.blocks.MediaUploadButton` component removed. Please use `wp.blocks.MediaUpload` component instead. diff --git a/docs/designers-developers/developers/block-api/block-annotations.md b/docs/designers-developers/developers/block-api/block-annotations.md index d74a300c54f0b7..e2d3f6408272e8 100644 --- a/docs/designers-developers/developers/block-api/block-annotations.md +++ b/docs/designers-developers/developers/block-api/block-annotations.md @@ -32,7 +32,7 @@ All available properties can be found in the API documentation of the `addAnnota The property `richTextIdentifier` is the identifier of the RichText instance the annotation applies to. This is necessary because blocks may have multiple rich text instances that are used to manage data for different attributes, so you need to pass this in order to highlight text within the correct one. -For example the Paragraph block only has a single RichText instance, with the identifer `content`. The quote block type has 2 RichText instances, so if you wish to highlight text in the citation, you need to pass `citation` as the `richTextIdentifier` when adding an annotation. To target the quote content, you need to use the identifier `value`. Refer to the source code of the block type to find the correct identifier. +For example the Paragraph block only has a single RichText instance, with the identifier `content`. The quote block type has 2 RichText instances, so if you wish to highlight text in the citation, you need to pass `citation` as the `richTextIdentifier` when adding an annotation. To target the quote content, you need to use the identifier `value`. Refer to the source code of the block type to find the correct identifier. ## Block annotation diff --git a/docs/designers-developers/developers/block-api/block-deprecation.md b/docs/designers-developers/developers/block-api/block-deprecation.md index ba1dd2f25b1f97..d598a157d4ab71 100644 --- a/docs/designers-developers/developers/block-api/block-deprecation.md +++ b/docs/designers-developers/developers/block-api/block-deprecation.md @@ -1,19 +1,47 @@ # Deprecated Blocks -When updating static blocks markup and attributes, block authors need to consider existing posts using the old versions of their block. In order to provide a good upgrade path, you can choose one of the following strategies: +When updating static blocks markup and attributes, block authors need to consider existing posts using the old versions of their block. To provide a good upgrade path, you can choose one of the following strategies: - Do not deprecate the block and create a new one (a different name) - Provide a "deprecated" version of the block allowing users opening these in the block editor to edit them using the updated block. -A block can have several deprecated versions. A deprecation will be tried if a parsed block appears to be invalid, or if there is a deprecation defined for which its `isEligible` property function returns true. +A block can have several deprecated versions. A deprecation will be tried if the current state of a parsed block is invalid, or if the deprecation defines an `isEligible` function that returns true. + +It is important to note that if a deprecation's `save` method does not produce a valid block then it is skipped, including its `migrate` method, even if `isEligible` would return true for the given attributes. This means that if you have several deprecations for a block and want to perform a new migration, like moving content to `InnerBlocks`, you may need to include the `migrate` method in multiple deprecations for it to be applied to all previous versions of the block. + +Deprecations do not operate as a chain of updates in the way other software data updates, like database migrations, do. At first glance, it is easy to think that each deprecation is going to make the required changes to the data and then hand this new form of the block onto the next deprecation to make its changes. What happens instead, is that each deprecation is passed the original saved content, and if its `save` method produces valid content the deprecation is used to parse the block attributes. If it has a `migrate` method it will also be run using the attributes parsed by the deprecation. The current block is updated with the migrated attributes and inner blocks before the current block's `save` function is run to generate new valid content for the block. At this point the current block should now be in a valid state. + +For blocks with multiple deprecations, it may be easier to save each deprecation to a constant with the version of the block it applies to, and then add each of these to the block's `deprecated` array. The deprecations in the array should be in reverse chronological order. This allows the block editor to attempt to apply the most recent and likely deprecations first, avoiding unnecessary and expensive processing. + +### Example: + +{% codetabs %} +{% ESNext %} +```js +const v1 = {}; +const v2 = {}; +const v3 = {}; +const deprecated = [ v3, v2, v1 ]; + +``` +{% ES5 %} +```js +var v1 = {}; +var v2 = {}; +var v3 = {}; +var deprecated = [ v3, v2, v1 ]; +``` +{% end %} + +It is also recommended to keep [fixtures](https://github.com/WordPress/gutenberg/blob/master/packages/e2e-tests/fixtures/blocks/README.md) which contain the different versions of the block content to allow you to easily test that new deprecations and migrations are working across all previous versions of the block. Deprecations are defined on a block type as its `deprecated` property, an array of deprecation objects where each object takes the form: - `attributes` (Object): The [attributes definition](/docs/designers-developers/developers/block-api/block-attributes.md) of the deprecated form of the block. - `supports` (Object): The [supports definition](/docs/designers-developers/developers/block-api/block-registration.md) of the deprecated form of the block. - `save` (Function): The [save implementation](/docs/designers-developers/developers/block-api/block-edit-save.md) of the deprecated form of the block. -- `migrate` (Function, Optional): A function which, given the old attributes and inner blocks is expected to return either the new attributes or a tuple array of `[ attributes, innerBlocks ]` compatible with the block. -- `isEligible` (Function, Optional): A function which, given the attributes and inner blocks of the parsed block, returns true if the deprecation can handle the block migration even if the block is valid. This function is not called when the block is invalid. This is particularly useful in cases where a block is technically valid even once deprecated, and requires updates to its attributes or inner blocks. +- `migrate` (Function, Optional): A function which, given the old attributes and inner blocks is expected to return either the new attributes or a tuple array of `[ attributes, innerBlocks ]` compatible with the block. As mentioned above, a deprecation's `migrate` will not be run if its `save` function does not return a valid block so you will need to make sure your migrations are available in all the deprecations where they are relevant. +- `isEligible` (Function, Optional): A function which, given the attributes and inner blocks of the parsed block, returns true if the deprecation can handle the block migration even if the block is valid. This is particularly useful in cases where a block is technically valid even once deprecated, and requires updates to its attributes or inner blocks. This function is not called when the results of all previous deprecations' `save` functions were invalid. It's important to note that `attributes`, `supports`, and `save` are not automatically inherited from the current version, since they can impact parsing and serialization of a block, so they must be defined on the deprecated object in order to be processed during a migration. diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index 5086bf7fdd3863..d51ae9f7820581 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -9,75 +9,118 @@ The `edit` function describes the structure of your block in the context of the {% codetabs %} {% ESNext %} ```jsx -edit: () => { - return
Your block.
; -} +import { useBlockProps } from '@wordpress/block-editor'; + +// ... +const blockSettings = { + apiVersion: 2, + + // ... + + edit: () => { + const blockProps = useBlockProps(); + + return
Your block.
; + } +}; ``` {% ES5 %} ```js -// A static div -edit: function() { - return wp.element.createElement( - 'div', - null, - 'Your block.' - ); -} +var blockSettings = { + apiVersion: 2, + + // ... + + edit: function() { + var blockProps = wp.blockEditor.useBlockProps(); + + return wp.element.createElement( + 'div', + blockProps, + 'Your block.' + ); + } +}; ``` {% end %} -The function receives the following properties through an object argument: +### block wrapper props -### attributes +The first thing to notice here is the use of the `useBlockProps` React hook on the block wrapper element. In the example above, the block wrapper renders a "div" in the editor, but in order for the Gutenberg editor to know how to manipulate the block, add any extra classNames that are needed for the block... the block wrapper element should apply props retrieved from the `useBlockProps` react hook call. -This property surfaces all the available attributes and their corresponding values, as described by the `attributes` property when the block type was registered. See [attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md) for how to specify attribute sources. - -In this case, assuming we had defined an attribute of `content` during block registration, we would receive and use that value in our edit function: +If the element wrapper needs any extra custom HTML attributes, these need to be passed as an argument to the `useBlockProps` hook. For example to add a `my-random-classname` className to the wrapper, you can use the following code: {% codetabs %} {% ESNext %} -```js -edit: ( { attributes } ) => { - return
{ attributes.content }
; -} +```jsx +import { useBlockProps } from '@wordpress/block-editor'; + +// ... +const blockSettings = { + apiVersion: 2, + + // ... + + edit: () => { + const blockProps = useBlockProps( { className: 'my-random-classname' } ); + + return
Your block.
; + } +}; ``` {% ES5 %} ```js -edit: function( props ) { - return wp.element.createElement( - 'div', - null, - props.attributes.content - ); -} +var blockSettings = { + apiVersion: 2, + + // ... + + edit: function() { + var blockProps = wp.blockEditor.useBlockProps( { className: 'my-random-classname' } ); + + return wp.element.createElement( + 'div', + blockProps, + 'Your block.' + ); + } +}; ``` {% end %} -The value of `attributes.content` will be displayed inside the `div` when inserting the block in the editor. +### attributes + +The `edit` function also receives a number of properties through an object argument. You can use these properties to adapt the behavior of your block. -### className +The `attributes` property surfaces all the available attributes and their corresponding values, as described by the `attributes` property when the block type was registered. See [attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md) for how to specify attribute sources. -This property returns the class name for the wrapper element. This is automatically added in the `save` method, but not on `edit`, as the root element may not correspond to what is _visually_ the main element of the block. You can request it to add it to the correct element in your function. +In this case, assuming we had defined an attribute of `content` during block registration, we would receive and use that value in our edit function: {% codetabs %} {% ESNext %} ```js -edit: ( { attributes, className } ) => { - return
{ attributes.content }
; +edit: ( { attributes } ) => { + const blockProps = useBlockProps(); + + return
{ attributes.content }
; } ``` {% ES5 %} ```js edit: function( props ) { + var blockProps = wp.blockEditor.useBlockProps(); + return wp.element.createElement( 'div', - { className: props.className }, + blockProps, props.attributes.content ); } ``` {% end %} +The value of `attributes.content` will be displayed inside the `div` when inserting the block in the editor. + ### isSelected The isSelected property is an object that communicates whether the block is currently selected. @@ -85,9 +128,11 @@ The isSelected property is an object that communicates whether the block is curr {% codetabs %} {% ESNext %} ```jsx -edit: ( { attributes, className, isSelected } ) => { +edit: ( { attributes, isSelected } ) => { + const blockProps = useBlockProps(); + return ( -
+
Your block. { isSelected && Shows only when the block is selected. @@ -99,9 +144,11 @@ edit: ( { attributes, className, isSelected } ) => { {% ES5 %} ```js edit: function( props ) { + var blockProps = wp.blockEditor.useBlockProps(); + return wp.element.createElement( 'div', - { className: props.className }, + blockProps, [ 'Your block.', props.isSelected ? wp.element.createElement( @@ -122,14 +169,16 @@ This function allows the block to update individual attributes based on user int {% codetabs %} {% ESNext %} ```jsx -edit: ( { attributes, setAttributes, className, isSelected } ) => { +edit: ( { attributes, setAttributes, isSelected } ) => { + const blockProps = useBlockProps(); + // Simplify access to attributes const { content, mySetting } = attributes; // Toggle a setting when the user clicks the button const toggleSetting = () => setAttributes( { mySetting: ! mySetting } ); return ( -
+
{ content } { isSelected && @@ -141,6 +190,8 @@ edit: ( { attributes, setAttributes, className, isSelected } ) => { {% ES5 %} ```js edit: function( props ) { + var blockProps = wp.blockEditor.useBlockProps(); + // Simplify access to attributes let content = props.attributes.content; let mySetting = props.attributes.mySetting; @@ -149,7 +200,7 @@ edit: function( props ) { let toggleSetting = () => props.setAttributes( { mySetting: ! mySetting } ); return wp.element.createElement( 'div', - { className: props.className }, + blockProps, [ content, props.isSelected ? wp.element.createElement( @@ -207,15 +258,19 @@ The `save` function defines the way in which the different attributes should be {% ESNext %} ```jsx save: () => { - return
Your block.
; + const blockProps = useBlockProps.save(); + + return
Your block.
; } ``` {% ES5 %} ```js save: function() { + var blockProps = wp.blockEditor.useBlockProps.save(); + return wp.element.createElement( 'div', - null, + blockProps, 'Your block.' ); } @@ -237,6 +292,10 @@ For [dynamic blocks](/docs/designers-developers/developers/tutorials/block-tutor If left unspecified, the default implementation will save no markup in post content for the dynamic block, instead deferring this to always be calculated when the block is shown on the front of the site. +### block wrapper props + +Like the `edit` function, when rendering static blocks, it's important to add the block props returned by `useBlockProps.save()` to the wrapper element of your block. This ensures that the block class name is rendered properly in addition to any HTML attribute injected by the block supports API. + ### attributes As with `edit`, the `save` function also receives an object argument including attributes which can be inserted into the markup. @@ -245,15 +304,19 @@ As with `edit`, the `save` function also receives an object argument including a {% ESNext %} ```jsx save: ( { attributes } ) => { - return
{ attributes.content }
; + const blockProps = useBlockProps.save(); + + return
{ attributes.content }
; } ``` {% ES5 %} ```js save: function( props ) { + var blockProps = wp.blockEditor.useBlockProps.save(); + return wp.element.createElement( 'div', - null, + blockProps, props.attributes.content ); } @@ -276,23 +339,30 @@ attributes: { content: { type: 'string', source: 'html', - selector: 'p' + selector: 'div' } }, edit: ( { attributes, setAttributes } ) => { + const blockProps = useBlockProps(); const updateFieldValue = ( val ) => { setAttributes( { content: val } ); } - return ; + return ( +
+ +

+ ); }, save: ( { attributes } ) => { - return

{ attributes.content }

; + const blockProps = useBlockProps.save(); + + return
{ attributes.content }
; }, ``` {% ES5 %} @@ -306,22 +376,30 @@ attributes: { }, edit: function( props ) { + var blockProps = wp.blockEditor.useBlockProps(); var updateFieldValue = function( val ) { props.setAttributes( { content: val } ); } + return wp.element.createElement( - wp.components.TextControl, - { - label: 'My Text Field', - value: props.attributes.content, - onChange: updateFieldValue, + 'div', + blockProps, + wp.element.createElement( + wp.components.TextControl, + { + label: 'My Text Field', + value: props.attributes.content, + onChange: updateFieldValue, - } + } + ) ); }, save: function( props ) { - return el( 'p', {}, props.attributes.content ); + var blockProps = wp.blockEditor.useBlockProps.save(); + + return wp.element.createElement( 'div', blockProps, props.attributes.content ); }, ``` {% end %} @@ -342,13 +420,18 @@ attributes: { }, edit: ( { attributes, setAttributes } ) => { - return { - setAttributes( { postsToShow: parseInt( val ) } ); - }}, - } + const blockProps = useBlockProps(); + + return ( +
+ { + setAttributes( { postsToShow: parseInt( val ) } ); + }} + /> +

); }, @@ -365,15 +448,21 @@ attributes: { }, edit: function( props ) { - return wp.element.createElement( - wp.components.TextControl, - { - label: 'Number Posts to Show', - value: props.attributes.postsToShow, - onChange: function( val ) { - props.setAttributes( { postsToShow: parseInt( val ) } ); - }, - } + var blockProps = wp.blockEditor.useBlockProps(); + + return wp.element.createEleement( + 'div', + blockProps, + wp.element.createElement( + wp.components.TextControl, + { + label: 'Number Posts to Show', + value: props.attributes.postsToShow, + onChange: function( val ) { + props.setAttributes( { postsToShow: parseInt( val ) } ); + }, + } + ) ); }, diff --git a/docs/designers-developers/developers/block-api/block-patterns.md b/docs/designers-developers/developers/block-api/block-patterns.md index dbcf18f973fa87..2987118a0479e4 100644 --- a/docs/designers-developers/developers/block-api/block-patterns.md +++ b/docs/designers-developers/developers/block-api/block-patterns.md @@ -14,9 +14,9 @@ The properties of the block pattern include: - `title` (required): A human-readable title for the pattern. - `content` (required): Raw HTML content for the pattern. - `description`: A visually hidden text used to describe the pattern in the inserter. A description is optional but it is strongly encouraged when the title does not fully describe what the pattern does. - - `categories`: A list of pattern categories used to group block patterns. Block patterns can be shown on multiple categories. - - `keywords`: Aliases or keywords that help users discover it while searching. - - `viewportWidth`: Specify the width of the pattern in the inserter. + - `categories`: An array of pattern categories used to group block patterns. Block patterns can be shown on multiple categories. + - `keywords`: An array of aliases or keywords that help users discover the pattern while searching. + - `viewportWidth`: An integer specifying the width of the pattern in the inserter. ```php register_block_pattern( diff --git a/docs/designers-developers/developers/block-api/block-registration.md b/docs/designers-developers/developers/block-api/block-registration.md index 711ca1acef0c38..46560971890650 100644 --- a/docs/designers-developers/developers/block-api/block-registration.md +++ b/docs/designers-developers/developers/block-api/block-registration.md @@ -178,11 +178,11 @@ The data provided in the example object should match the attributes defined. For ```js example: { - attributes: { - cover: 'https://example.com/image.jpg', - author: 'William Shakespeare', - pages: 500 - }, + attributes: { + cover: 'https://example.com/image.jpg', + author: 'William Shakespeare', + pages: 500 + }, }, ``` @@ -192,20 +192,31 @@ It's also possible to extend the block preview with inner blocks via `innerBlock ```js example: { - attributes: { - cover: 'https://example.com/image.jpg', - }, - innerBlocks: [ - { - name: 'core/paragraph', - attributes: { - /* translators: example text. */ - content: __( - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent et eros eu felis.' - ), - }, - }, - ], + attributes: { + cover: 'https://example.com/image.jpg', + }, + innerBlocks: [ + { + name: 'core/paragraph', + attributes: { + /* translators: example text. */ + content: __( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent et eros eu felis.' + ), + }, + }, + ], +}, +``` + +It's also possible to define the width of the preview container in pixels via `viewportWidth`. For example: + +```js +example: { + attributes: { + cover: 'https://example.com/image.jpg', + }, + viewportWidth: 800 }, ``` @@ -253,7 +264,10 @@ An object describing a variation defined for the block type can contain the foll - `attributes` (optional, type `Object`) – Values that override block attributes. - `innerBlocks` (optional, type `Array[]`) – Initial configuration of nested blocks. - `example` (optional, type `Object`) – Example provides structured data for the block preview. You can set to `undefined` to disable the preview shown for the block type. -- `scope` (optional, type `string[]`) - the list of scopes where the variation is applicable. When not provided, it assumes all available scopes. Available options: `block`, `inserter`. +- `scope` (optional, type `WPBlockVariationScope[]`) - the list of scopes where the variation is applicable. When not provided, it defaults to `block` and `inserter`. Available options: + - `inserter` - Block Variation is shown on the inserter. + - `block` - Used by blocks to filter specific block variations. Mostly used in Placeholder patterns like `Columns` block. + - `transform` - Block Variation will be shown in the component for Block Variations transformations. - `keywords` (optional, type `string[]`) - An array of terms (which can be translated) that help users discover the variation while searching. It's also possible to override the default block style variation using the `className` attribute when defining block variations. diff --git a/docs/designers-developers/developers/block-api/block-supports.md b/docs/designers-developers/developers/block-api/block-supports.md index d0e459257d933b..cd93af21096276 100644 --- a/docs/designers-developers/developers/block-api/block-supports.md +++ b/docs/designers-developers/developers/block-api/block-supports.md @@ -80,6 +80,92 @@ supports: { } ``` +## color + +- Type: `Object` +- Default value: null +- Subproperties: + - `background`: type `boolean`, default value `true` + - `gradient`: type `boolean`, default value `false` + - `text`: type `boolean`, default value `true` + +This value signals that a block supports some of the CSS style properties related to color. When it does, the block editor will show UI controls for the user to set their values. + +The controls for background and text will source their colors from the `editor-color-palette` [theme support](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-color-palettes), while the gradient's from `editor-gradient-presets` [theme support](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-gradient-presets). + +Note that the `text` and `background` keys have a default value of `true`, so if the `color` property is present they'll also be considered enabled: + +```js +supports: { + color: { // This also enables text and background UI controls. + gradient: true // Enable gradients UI control. + } +} +``` + +It's possible to disable them individually: + +```js +supports: { + color: { // Text UI control is enabled. + background: false, // Disable background UI control. + gradient: true // Enable gradients UI control. + } +} +``` + +When the block has support for a specific color property, the attributes definition is extended to include some attributes. + +- `style`: attribute of `object` type with no default assigned. This is added when any of support color properties are declared. It stores the custom values set by the user. The block can apply a default style by specifying its own `style` attribute with a default e.g.: + +```js +attributes: { + style: { + type: 'object', + default: { + color: { + background: 'value', + gradient: 'value', + text: 'value' + } + } + } +} +``` + +- When `background` support is declared: it'll be added a new `backgroundColor` attribute of type `string` with no default assigned. It stores the preset values set by the user. The block can apply a default background color by specifying its own attribute with a default e.g.: + +```js +attributes: { + backgroundColor: { + type: 'string', + default: 'some-value', + } +} +``` + +- When `gradient` support is declared: it'll be added a new `gradient` attribute of type `string` with no default assigned. It stores the preset values set by the user. The block can apply a default text color by specifying its own attribute with a default e.g.: + +```js +attributes: { + gradient: { + type: 'string', + default: 'some-value', + } +} +``` + +- When `text` support is declared: it'll be added a new `textColor` attribute of type `string` with no default assigned. It stores the preset values set by the user. The block can apply a default text color by specifying its own attribute with a default e.g.: + +```js +attributes: { + textColor: { + type: 'string', + default: 'some-value', + } +} +``` + ## customClassName - Type: `boolean` @@ -108,6 +194,50 @@ supports: { } ``` +## fontSize + +- Type: `boolean` +- Default value: `false` + +This value signals that a block supports the font-size CSS style property. When it does, the block editor will show an UI control for the user to set its value. + +The values shown in this control are the ones declared by the theme via the `editor-font-sizes` [theme support](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-font-sizes), or the default ones if none is provided. + +```js +supports: { + // Enable UI control for font-size. + fontSize: true, +} +``` + +When the block declares support for `fontSize`, the attributes definition is extended to include two new attributes: `fontSize` and `style`: + +- `fontSize`: attribute of `string` type with no default assigned. It stores the preset values set by the user. The block can apply a default fontSize by specifying its own `fontSize` attribute with a default e.g.: + +```js +attributes: { + fontSize: { + type: 'string', + default: 'some-value', + } +} +``` + +- `style`: attribute of `object` type with no default assigned. It stores the custom values set by the user. The block can apply a default style by specifying its own `style` attribute with a default e.g.: + +```js +attributes: { + style: { + type: 'object', + default: { + typography: { + fontSize: 'value' + } + } + } +} +``` + ## html - Type: `boolean` @@ -136,6 +266,35 @@ supports: { } ``` +## lineHeight + +- Type: `boolean` +- Default value: `false` + +This value signals that a block supports the line-height CSS style property. When it does, the block editor will show an UI control for the user to set its value if [the theme declares support](/docs/designers-developers/developers/themes/theme-support.md#supporting-custom-line-heights). + +```js +supports: { + // Enable UI control for line-height. + lineHeight: true, +} +``` + +When the block declares support for `lineHeight`, the attributes definition is extended to include a new attribute `style` of `object` type with no default assigned. It stores the custom value set by the user. The block can apply a default style by specifying its own `style` attribute with a default e.g.: + +```js +attributes: { + style: { + type: 'object', + default: { + typography: { + lineHeight: 'value' + } + } + } +} +``` + ## multiple - Type: `boolean` @@ -163,3 +322,40 @@ supports: { reusable: false } ``` + +## spacing + +- Type: `Object` +- Default value: null +- Subproperties: + - `padding`: type `boolean`, default value `false` + +This value signals that a block supports some of the CSS style properties related to spacing. When it does, the block editor will show UI controls for the user to set their values, if [the theme declares support](/docs/designers-developers/developers/themes/theme-support.md##cover-block-padding). + +```js +supports: { + padding: true, // Enable padding color UI control. +} +``` + +When the block declares support for a specific spacing property, the attributes definition is extended to include some attributes. + +- `style`: attribute of `object` type with no default assigned. This is added when `padding` support is declared. It stores the custom values set by the user. The block can apply a default style by specifying its own `style` attribute with a default e.g.: + +```js +attributes: { + style: { + type: 'object', + default: { + spacing: { + padding: { + top: 'value', + right: 'value', + bottom: 'value', + left: 'value' + } + } + } + } +} +``` diff --git a/docs/designers-developers/developers/block-api/block-transforms.md b/docs/designers-developers/developers/block-api/block-transforms.md index 53cffab5a1a50e..ad4f7fcc903477 100644 --- a/docs/designers-developers/developers/block-api/block-transforms.md +++ b/docs/designers-developers/developers/block-api/block-transforms.md @@ -290,7 +290,7 @@ transforms: { ### Raw -This type of transformations support the _from_ direction, allowing blocks to be created from raw HTML nodes. They're applied when the user executes the "Convert to Blocks" action frow within the block setting UI menu, as well as when some content is pasted or dropped into the editor. +This type of transformations support the _from_ direction, allowing blocks to be created from raw HTML nodes. They're applied when the user executes the "Convert to Blocks" action from within the block setting UI menu, as well as when some content is pasted or dropped into the editor. A transformation of type `raw` is an object that takes the following parameters: diff --git a/docs/designers-developers/developers/block-api/versions.md b/docs/designers-developers/developers/block-api/versions.md new file mode 100644 index 00000000000000..cd78b5873dba66 --- /dev/null +++ b/docs/designers-developers/developers/block-api/versions.md @@ -0,0 +1,12 @@ +# Block API Versions + +This document lists the changes made between the different API versions. + +## Version 2 (>= WordPress 5.6) + +- To render the block element wrapper for the block's `edit` implementation, the block author must use the `useBlockProps()` hook. +- The generated class names and styles are no longer added automatically to the saved markup for static blocks when `save` is processed. To include them, the block author must explicitly use `useBlockProps.save()` and add to their block wrapper. + +## Version 1 + +Initial version. \ No newline at end of file diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md index dd1c3d896137d8..3ab9d0bd1cd3ec 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -1134,10 +1134,6 @@ _Parameters_ - _firstBlockClientId_ `string`: Client ID of the first block to merge. - _secondBlockClientId_ `string`: Client ID of the second block to merge. -_Returns_ - -- `Object`: Action object. - # **moveBlocksDown** Undocumented declaration. @@ -1179,10 +1175,6 @@ _Parameters_ - _start_ `string`: First block of the multi selection. - _end_ `string`: Last block of the multiselection. -_Returns_ - -- `Object`: Action object. - # **receiveBlocks** Returns an action object used in signalling that blocks have been received. @@ -1257,7 +1249,7 @@ _Parameters_ - _rootClientId_ `string`: Client ID of the block whose InnerBlocks will re replaced. - _blocks_ `Array`: Block objects to insert as new InnerBlocks -- _updateSelection_ `?boolean`: If true block selection will be updated. If false, block selection will not change. Defaults to true. +- _updateSelection_ `?boolean`: If true block selection will be updated. If false, block selection will not change. Defaults to false. _Returns_ @@ -1273,10 +1265,6 @@ _Parameters_ - _blocks_ `Array`: Array of blocks. -_Returns_ - -- `Object`: Action object. - # **resetSelection** Returns an action object used in signalling that selection state should be @@ -1540,5 +1528,16 @@ _Returns_ - `Object`: Action object +# **validateBlocksToTemplate** + +Block validity is a function of blocks state (at the point of a +reset) and the template setting. As a compromise to its placement +across distinct parts of state, it is implemented here as a side- +effect of the block reset action. + +_Parameters_ + +- _blocks_ `Array`: Array of blocks. + diff --git a/docs/designers-developers/developers/data/data-core-edit-post.md b/docs/designers-developers/developers/data/data-core-edit-post.md index 85e5d162c0bf97..ddaac2ac0ca707 100644 --- a/docs/designers-developers/developers/data/data-core-edit-post.md +++ b/docs/designers-developers/developers/data/data-core-edit-post.md @@ -110,6 +110,18 @@ _Returns_ - `boolean`: Whether there are metaboxes or not. +# **isEditingTemplate** + +Returns true if the template editing mode is enabled. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: Whether we're editing the template. + # **isEditorPanelEnabled** Returns true if the given panel is enabled, or false otherwise. Panels are @@ -372,10 +384,6 @@ _Returns_ Returns an action object used to request meta box update. -_Returns_ - -- `Object`: Action object. - # **setAvailableMetaBoxesPerLocation** Returns an action object used in signaling @@ -385,6 +393,14 @@ _Parameters_ - _metaBoxesPerLocation_ `Object`: Meta boxes per location. +# **setIsEditingTemplate** + +Returns an action object used to switch to template editing. + +_Parameters_ + +- _value_ `boolean`: Is editing template. + _Returns_ - `Object`: Action object. diff --git a/docs/designers-developers/developers/data/data-core-notices.md b/docs/designers-developers/developers/data/data-core-notices.md index 54dfbdd8d84ed8..0ecaa5cc3a7a19 100644 --- a/docs/designers-developers/developers/data/data-core-notices.md +++ b/docs/designers-developers/developers/data/data-core-notices.md @@ -77,6 +77,9 @@ _Parameters_ - _options.type_ `[string]`: Type of notice, one of `default`, or `snackbar`. - _options.speak_ `[boolean]`: Whether the notice content should be announced to screen readers. - _options.actions_ `[Array]`: User actions to be presented with notice. +- _options.icon_ `[Object]`: An icon displayed with the notice. +- _options.explicitDismiss_ `[boolean]`: Whether the notice includes an explict dismiss button and can't be dismissed by clicking the body of the notice. +- _options.onDismiss_ `[Function]`: Called when the notice is dismissed. _Returns_ diff --git a/docs/designers-developers/developers/data/data-core.md b/docs/designers-developers/developers/data/data-core.md index 6484e914f2eefe..37c4ad7d5f8584 100644 --- a/docs/designers-developers/developers/data/data-core.md +++ b/docs/designers-developers/developers/data/data-core.md @@ -34,6 +34,7 @@ Returns all available authors. _Parameters_ - _state_ `Object`: Data state. +- _query_ `(Object|undefined)`: Optional object of query parameters to include with request. _Returns_ diff --git a/docs/designers-developers/developers/feature-flags.md b/docs/designers-developers/developers/feature-flags.md index 47a28d0fa42c39..7f7a165d13a3ac 100644 --- a/docs/designers-developers/developers/feature-flags.md +++ b/docs/designers-developers/developers/feature-flags.md @@ -71,7 +71,7 @@ if ( 2 === 2 ) { } ``` - The condition will alway evaluates to `true`, so can be removed leaving just the code in the body: + The condition will always evaluates to `true`, so can be removed leaving just the code in the body: ```js phaseTwoFeature(); ``` diff --git a/docs/designers-developers/developers/internationalization.md b/docs/designers-developers/developers/internationalization.md index bc97e97f48348b..50bc9c12268d8a 100644 --- a/docs/designers-developers/developers/internationalization.md +++ b/docs/designers-developers/developers/internationalization.md @@ -24,10 +24,11 @@ function myguten_block_init() { wp_register_script( 'myguten-script', plugins_url( 'block.js', __FILE__ ), - array( 'wp-blocks', 'wp-element', 'wp-i18n' ) + array( 'wp-blocks', 'wp-element', 'wp-i18n', 'wp-block-editor' ) ); register_block_type( 'myguten/simple', array( + 'apiVersion' => 2, 'editor_script' => 'myguten-script', ) ); } @@ -41,22 +42,28 @@ In your code, you can include the i18n functions. The most common function is ** ```js import { __ } from '@wordpress/i18n'; import { registerBlockType } from '@wordpress/blocks'; +import { useBlockProps } from '@wordpress/block-editor'; registerBlockType( 'myguten/simple', { + apiVersion: 2, title: __( 'Simple Block', 'myguten' ), category: 'widgets', edit: () => { + const blockProps = useBlockProps( { style: { color: 'red' } } ); + return ( -

+

{ __( 'Hello World', 'myguten' ) }

); }, save: () => { + const blockProps = useBlockProps.save( { style: { color: 'red' } } ); + return ( -

+

{ __( 'Hello World', 'myguten' ) }

); @@ -68,23 +75,27 @@ registerBlockType( 'myguten/simple', { const { __ } = wp.i18n; const el = wp.element.createElement; const { registerBlockType } = wp.blocks; +const { useBlockProps } = wp.blockEditor; registerBlockType( 'myguten/simple', { title: __( 'Simple Block', 'myguten' ), category: 'widgets', edit: function() { + const blockProps = useBlockProps( { style: { color: 'red' } } ); + return el( 'p', - { style: { color: 'red' } }, + blockProps, __( 'Hello World', 'myguten' ) ); }, save: function() { + const blockProps = useBlockProps.save( { style: { color: 'red' } } ); return el( 'p', - { style: { color: 'red' } }, + blockProps, __( 'Hello World', 'myguten' ) ); }, diff --git a/docs/designers-developers/developers/platform/custom-block-editor/tutorial.md b/docs/designers-developers/developers/platform/custom-block-editor/tutorial.md index 394007c794cd48..82866693efd2f2 100644 --- a/docs/designers-developers/developers/platform/custom-block-editor/tutorial.md +++ b/docs/designers-developers/developers/platform/custom-block-editor/tutorial.md @@ -37,7 +37,7 @@ Our editor will have the following features: * Ability to add and edit all Core Blocks. * Familiar visual styles and main/sidebar layout. -* _Basic_ block persistance between page reloads. +* _Basic_ block persistence between page reloads. With that in mind, let's start taking our first steps towards building this. @@ -131,7 +131,7 @@ With our target HTML in place we can now enqueue some JavaScript (as well as som To do this we hook into `admin_enqueue_scripts`. -First, we meed to make sure we only run our custom code on our own admin page, so at the top of our callback function let's exit early if the page doesn't match our page's identifier: +First, we need to make sure we only run our custom code on our own admin page, so at the top of our callback function let's exit early if the page doesn't match our page's identifier: ```php // init.php @@ -175,7 +175,7 @@ wp_enqueue_style( 'getdave-sbe-styles', // Handle. plugins_url( 'build/index.css', __FILE__ ), // Block editor CSS. array( 'wp-edit-blocks' ), // Dependency to include the CSS after it. - filemtime( dirname( __FILE__ ) . '/build/index.css' ) + filemtime( __DIR__ . '/build/index.css' ) ); ``` diff --git a/docs/designers-developers/developers/richtext.md b/docs/designers-developers/developers/richtext.md index 3909306a92d6d2..e565780a48bb24 100644 --- a/docs/designers-developers/developers/richtext.md +++ b/docs/designers-developers/developers/richtext.md @@ -29,7 +29,7 @@ There are a number of core blocks using the RichText component. The JavaScript e {% ESNext %} ```js import { registerBlockType } from '@wordpress/blocks'; -import { RichText } from '@wordpress/block-editor'; +import { useBlockProps, RichText } from '@wordpress/block-editor'; registerBlockType( /* ... */, { // ... @@ -42,11 +42,13 @@ registerBlockType( /* ... */, { }, }, - edit( { className, attributes, setAttributes } ) { + edit( { attributes, setAttributes } ) { + const blockProps = useBlockProps(); + return ( setAttributes( { content } ) } // Store updated content as a block attribute @@ -56,7 +58,9 @@ registerBlockType( /* ... */, { }, save( { attributes } ) { - return ; // Saves

Content added in the editor...

to the database for frontend display + const blockProps = useBlockProps.save(); + + return ; // Saves

Content added in the editor...

to the database for frontend display } } ); ``` @@ -74,22 +78,25 @@ wp.blocks.registerBlockType( /* ... */, { }, edit: function( props ) { - return wp.element.createElement( wp.editor.RichText, { + var blockProps = wp.blockEditor.useBlockProps(); + + return wp.element.createElement( wp.blockEditor.RichText, Object.assign( blockProps, { tagName: 'h2', // The tag here is the element output and editable in the admin - className: props.className, value: props.attributes.content, // Any existing content, either from the database or an attribute default formattingControls: [ 'bold', 'italic' ], // Allow the content to be made bold or italic, but do not allow other formatting options onChange: function( content ) { props.setAttributes( { content: content } ); // Store updated content as a block attribute }, placeholder: __( 'Heading...' ), // Display this text before any content has been added by the user - } ); + } ) ); }, save: function( props ) { - return wp.element.createElement( wp.editor.RichText.Content, { + var blockProps = wp.blockEditor.useBlockProps.save(); + + return wp.element.createElement( wp.blockEditor.RichText.Content, Object.assign( blockProps, { tagName: 'h2', value: props.attributes.content // Saves

Content added in the editor...

to the database for frontend display - } ); + } ) ); } } ); ``` diff --git a/docs/designers-developers/developers/slotfills/README.md b/docs/designers-developers/developers/slotfills/README.md index 3ab5536f55f823..2ed99cf47b3e15 100644 --- a/docs/designers-developers/developers/slotfills/README.md +++ b/docs/designers-developers/developers/slotfills/README.md @@ -95,7 +95,7 @@ const PostStatus = ( { isOpened, onTogglePanel } ) => ( ## Currently available SlotFills and examples -There are currently eight available SlotFills in the `edit-post` package. Please refer to the individual items below for usage and example details: +The following SlotFills are available in the `edit-post` package. Please refer to the individual items below for usage and example details: * [MainDashboardButton](/docs/designers-developers/developers/slotfills/main-dashboard-button.md) * [PluginBlockSettingsMenuItem](/docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md) diff --git a/docs/designers-developers/developers/slotfills/main-dashboard-button.md b/docs/designers-developers/developers/slotfills/main-dashboard-button.md index 3a10665f21ad8e..3392eed131218f 100644 --- a/docs/designers-developers/developers/slotfills/main-dashboard-button.md +++ b/docs/designers-developers/developers/slotfills/main-dashboard-button.md @@ -1,15 +1,19 @@ # MainDashboardButton -This slot allows replacing the default main dashboard button that's used for closing -the editor in fullscreen mode. +This slot allows replacing the default main dashboard button in the post editor and site editor. +It's used for returning back to main wp-admin dashboard when editor is in fullscreen mode. -## Example +## Examples + +### Post editor example + +This will override the W icon button in the header. ```js import { registerPlugin } from '@wordpress/plugins'; import { __experimentalMainDashboardButton as MainDashboardButton, -} from '@wordpress/interface'; +} from '@wordpress/edit-post'; const MainDashboardButtonTest = () => ( @@ -22,23 +26,49 @@ registerPlugin( 'main-dashboard-button-test', { } ); ``` -If your goal is just to replace the icon of the existing button, that can be achieved -in the following way: +If your goal is just to replace the icon of the existing button in +the post editor, that can be achieved in the following way: ```js import { registerPlugin } from '@wordpress/plugins'; import { __experimentalFullscreenModeClose as FullscreenModeClose, -} from '@wordpress/edit-site'; -import { __experimentalMainDashboardButton as MainDashboardButton, -} from '@wordpress/interface'; +} from '@wordpress/edit-post'; import { close } from '@wordpress/icons'; const MainDashboardButtonIconTest = () => ( - + + +); + +registerPlugin( 'main-dashboard-button-icon-test', { + render: MainDashboardButtonIconTest, +} ); +``` + +### Site editor example + +In the site editor this slot refers to the "back to dashboard" button in the navigation sidebar. + +```js +import { registerPlugin } from '@wordpress/plugins'; +import { + __experimentalMainDashboardButton as MainDashboardButton, +} from '@wordpress/edit-site'; +import { + __experimentalNavigationBackButton as NavigationBackButton, +} from '@wordpress/components'; + +const MainDashboardButtonIconTest = () => ( + + ); diff --git a/docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md b/docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md index f96df587dc6243..3d3dec0f01ec8e 100644 --- a/docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md +++ b/docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md @@ -28,4 +28,3 @@ registerPlugin( 'block-settings-menu-group-test', { ## Location ![Location](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/plugin-block-settings-menu-item-screenshot.png?raw=true "PluginBlockSettingsMenuItem Location") - diff --git a/docs/designers-developers/developers/themes/block-based-themes.md b/docs/designers-developers/developers/themes/block-based-themes.md index 03f4f278b9a256..34d37f32c403d8 100644 --- a/docs/designers-developers/developers/themes/block-based-themes.md +++ b/docs/designers-developers/developers/themes/block-based-themes.md @@ -4,7 +4,7 @@ > Documentation has been shared early to surface what’s being worked on and invite feedback from those experimenting with the APIs. You can provide feedback in the weekly #core-editor chats where the latest progress of this effort will be shared and discussed, or async via Github issues. -**Note:** In order to use these features, make sure to enable the "Full Site Editing" flag from the **Experiments** page of the Gutenberg plugin. +**Note:** To use these features, activate a theme that includes a `block-templates/index.html` file. This will signal to the block editor that the theme should use full-site editing features. ## What is a block-based theme? diff --git a/docs/designers-developers/developers/themes/theme-json.md b/docs/designers-developers/developers/themes/theme-json.md index 58a4ca6cb27578..b66cd3d621439c 100644 --- a/docs/designers-developers/developers/themes/theme-json.md +++ b/docs/designers-developers/developers/themes/theme-json.md @@ -57,9 +57,9 @@ Every context has the same structure, divided in two sections: `settings` and `s "some/context": { "settings": { "color": [ ... ], - "typography": [ ... ], + "custom": [ ... ], "spacing": [ ... ], - "custom": [ ... ] + "typography": [ ... ] }, "styles": { "color": { ... }, @@ -86,17 +86,24 @@ The settings section has the following structure and default values: "link": false, /* true to opt-in, as in add_theme_support('experimental-link-color') */ "palette": [ ... ], /* color presets, as in add_theme_support('editor-color-palette', ... ) */ }, + "custom": { ... }, "spacing": { "customPadding": false, /* true to opt-in, as in add_theme_support('custom-spacing') */ "units": [ "px", "em", "rem", "vh", "vw" ], /* filter values, as in add_theme_support('custom-units', ... ) */ }, "typography": { "customFontSize": true, /* false to opt-out, as in add_theme_support( 'disable-custom-font-sizes' ) */ + "customFontWeight": true, /* false to opt-out */ + "customFontStyle": true, /* false to opt-out */ "customLineHeight": false, /* true to opt-in, as in add_theme_support( 'custom-line-height' ) */ "dropCap": true, /* false to opt-out */ + "fontFamilies": [ ... ], /* font family presets */ "fontSizes": [ ... ], /* font size presets, as in add_theme_support('editor-font-sizes', ... ) */ - }, - "custom": { ... } + "fontStyles": [ ... ], /* font style presets */ + "fontWeights": [ ... ], /* font weight presets */ + "textDecorations": [ ... ], /* text decoration presets */ + "textTransforms": [ ... ] /* text transform presets */ + } } } } @@ -230,19 +237,32 @@ Note that, the name of the variable is created by adding `--` in between each ne Each block declares which style properties it exposes. This has been coined as "implicit style attributes" of the block. These properties are then used to automatically generate the UI controls for the block in the editor, as well as being available through the `experimental-theme.json` file for themes to target. -``` +```json { "some/context": { "styles": { "color": { - "background": , - "gradient": , - "link": , - "text": + "background": "value", + "gradient": "value", + "link": "value", + "text": "value" + }, + "spacing": { + "padding": { + "top": "value", + "right": "value", + "bottom": "value", + "left": "value", + }, }, "typography": { - "fontSize": , - "lineHeight": + "fontFamily": "value", + "fontSize": "value", + "fontStyle": "value", + "fontWeight": "value", + "lineHeight": "value", + "textDecoration": "value", + "textTransform": "value" } } } @@ -299,7 +319,9 @@ These are the current color properties supported by blocks: | Columns | Yes | Yes | Yes | Yes | | Group | Yes | Yes | Yes | Yes | | Heading [1] | Yes | - | Yes | Yes | +| List | Yes | Yes | - | Yes | | Media & text | Yes | Yes | Yes | Yes | +| Navigation | Yes | - | - | Yes | | Paragraph | Yes | - | Yes | Yes | | Post Author | Yes | Yes | Yes | Yes | | Post Comments | Yes | Yes | Yes | Yes | @@ -307,34 +329,46 @@ These are the current color properties supported by blocks: | Post Comments Form | Yes | Yes | Yes | Yes | | Post Date | Yes | Yes | - | Yes | | Post Excerpt | Yes | Yes | Yes | Yes | +| Post Hierarchical Terms | Yes | Yes | Yes | Yes | | Post Tags | Yes | Yes | Yes | Yes | | Post Title | Yes | Yes | - | Yes | | Site Tagline | Yes | Yes | - | Yes | | Site Title | Yes | Yes | - | Yes | +| Template Part | Yes | Yes | Yes | Yes | [1] The heading block represents 6 distinct HTML elements: H1-H6. It comes with selectors to target each individual element (ex: core/heading/h1 for H1, etc). +#### Spacing Properties + +| Context | Padding | +| --- | --- | +| Cover | Yes | +| Group | Yes | + #### Typography Properties These are the current typography properties supported by blocks: -| Context | Font Size | Line Height | -| --- | --- | --- | -| Global | Yes | - | -| Columns | - | - | -| Group | - | - | -| Heading [1] | Yes | Yes | -| Media & text | - | - | -| Paragraph | Yes | Yes | -| Post Author | Yes | Yes | -| Post Comments | Yes | Yes | -| Post Comments Count | Yes | Yes | -| Post Comments Form | Yes | Yes | -| Post Date | Yes | Yes | -| Post Excerpt | Yes | Yes | -| Post Tags | Yes | Yes | -| Post Title | Yes | Yes | -| Site Tagline | Yes | Yes | -| Site Title | Yes | Yes | +| Context | Font Family | Font Size | Font Style | Font Weight | Line Height | Text Decoration | Text Transform | +| --- | --- | --- | --- | --- | --- | --- | --- | +| Global | Yes | Yes | Yes | Yes | Yes | Yes | Yes | +| Code | - | Yes | - | - | - | - | - | +| Heading [1] | - | Yes | - | - | Yes | - | - | +| List | - | Yes | - | - | - | - | - | +| Navigation | Yes | Yes | Yes | Yes | - | Yes | Yes | +| Paragraph | - | Yes | - | - | Yes | - | - | +| Post Author | - | Yes | - | - | Yes | - | - | +| Post Comments | - | Yes | - | - | Yes | - | - | +| Post Comments Count | - | Yes | - | - | Yes | - | - | +| Post Comments Form | - | Yes | - | - | Yes | - | - | +| Post Date | - | Yes | - | - | Yes | - | - | +| Post Excerpt | - | Yes | - | - | Yes | - | - | +| Post Hierarchical Terms | - | Yes | - | - | Yes | - | - | +| Post Tags | - | Yes | - | - | Yes | - | - | +| Post Title | Yes | Yes | - | - | Yes | - | - | +| Preformatted | - | Yes | - | - | - | - | - | +| Site Tagline | Yes | Yes | - | - | Yes | - | - | +| Site Title | Yes | Yes | - | - | Yes | - | - | +| Verse | Yes | Yes | - | - | - | - | - | [1] The heading block represents 6 distinct HTML elements: H1-H6. It comes with selectors to target each individual element (ex: core/heading/h1 for H1, etc). diff --git a/docs/designers-developers/developers/themes/theme-support.md b/docs/designers-developers/developers/themes/theme-support.md index 8331a41d2d935b..5f022bca7094db 100644 --- a/docs/designers-developers/developers/themes/theme-support.md +++ b/docs/designers-developers/developers/themes/theme-support.md @@ -22,22 +22,22 @@ To opt-in for one of these features, call `add_theme_support` in the `functions. function mytheme_setup_theme_supported_features() { add_theme_support( 'editor-color-palette', array( array( - 'name' => __( 'strong magenta', 'themeLangDomain' ), + 'name' => esc_attr__( 'strong magenta', 'themeLangDomain' ), 'slug' => 'strong-magenta', 'color' => '#a156b4', ), array( - 'name' => __( 'light grayish magenta', 'themeLangDomain' ), + 'name' => esc_attr__( 'light grayish magenta', 'themeLangDomain' ), 'slug' => 'light-grayish-magenta', 'color' => '#d0a5db', ), array( - 'name' => __( 'very light gray', 'themeLangDomain' ), + 'name' => esc_attr__( 'very light gray', 'themeLangDomain' ), 'slug' => 'very-light-gray', 'color' => '#eee', ), array( - 'name' => __( 'very dark gray', 'themeLangDomain' ), + 'name' => esc_attr__( 'very dark gray', 'themeLangDomain' ), 'slug' => 'very-dark-gray', 'color' => '#444', ), @@ -104,22 +104,22 @@ Different blocks have the possibility of customizing colors. The block editor pr ```php add_theme_support( 'editor-color-palette', array( array( - 'name' => __( 'strong magenta', 'themeLangDomain' ), + 'name' => esc_attr__( 'strong magenta', 'themeLangDomain' ), 'slug' => 'strong-magenta', 'color' => '#a156b4', ), array( - 'name' => __( 'light grayish magenta', 'themeLangDomain' ), + 'name' => esc_attr__( 'light grayish magenta', 'themeLangDomain' ), 'slug' => 'light-grayish-magenta', 'color' => '#d0a5db', ), array( - 'name' => __( 'very light gray', 'themeLangDomain' ), + 'name' => esc_attr__( 'very light gray', 'themeLangDomain' ), 'slug' => 'very-light-gray', 'color' => '#eee', ), array( - 'name' => __( 'very dark gray', 'themeLangDomain' ), + 'name' => esc_attr__( 'very dark gray', 'themeLangDomain' ), 'slug' => 'very-dark-gray', 'color' => '#444', ), @@ -155,27 +155,27 @@ add_theme_support( 'editor-gradient-presets', array( array( - 'name' => __( 'Vivid cyan blue to vivid purple', 'themeLangDomain' ), + 'name' => esc_attr__( 'Vivid cyan blue to vivid purple', 'themeLangDomain' ), 'gradient' => 'linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)', 'slug' => 'vivid-cyan-blue-to-vivid-purple' ), array( - 'name' => __( 'Vivid green cyan to vivid cyan blue', 'themeLangDomain' ), + 'name' => esc_attr__( 'Vivid green cyan to vivid cyan blue', 'themeLangDomain' ), 'gradient' => 'linear-gradient(135deg,rgba(0,208,132,1) 0%,rgba(6,147,227,1) 100%)', 'slug' => 'vivid-green-cyan-to-vivid-cyan-blue', ), array( - 'name' => __( 'Light green cyan to vivid green cyan', 'themeLangDomain' ), + 'name' => esc_attr__( 'Light green cyan to vivid green cyan', 'themeLangDomain' ), 'gradient' => 'linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)', 'slug' => 'light-green-cyan-to-vivid-green-cyan', ), array( - 'name' => __( 'Luminous vivid amber to luminous vivid orange', 'themeLangDomain' ), + 'name' => esc_attr__( 'Luminous vivid amber to luminous vivid orange', 'themeLangDomain' ), 'gradient' => 'linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%)', 'slug' => 'luminous-vivid-amber-to-luminous-vivid-orange', ), array( - 'name' => __( 'Luminous vivid orange to vivid red', 'themeLangDomain' ), + 'name' => esc_attr__( 'Luminous vivid orange to vivid red', 'themeLangDomain' ), 'gradient' => 'linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%)', 'slug' => 'luminous-vivid-orange-to-vivid-red', ), @@ -201,22 +201,22 @@ Blocks may allow the user to configure the font sizes they use, e.g., the paragr ```php add_theme_support( 'editor-font-sizes', array( array( - 'name' => __( 'Small', 'themeLangDomain' ), + 'name' => esc_attr__( 'Small', 'themeLangDomain' ), 'size' => 12, 'slug' => 'small' ), array( - 'name' => __( 'Regular', 'themeLangDomain' ), + 'name' => esc_attr__( 'Regular', 'themeLangDomain' ), 'size' => 16, 'slug' => 'regular' ), array( - 'name' => __( 'Large', 'themeLangDomain' ), + 'name' => esc_attr__( 'Large', 'themeLangDomain' ), 'size' => 36, 'slug' => 'large' ), array( - 'name' => __( 'Huge', 'themeLangDomain' ), + 'name' => esc_attr__( 'Huge', 'themeLangDomain' ), 'size' => 50, 'slug' => 'huge' ) @@ -388,9 +388,9 @@ To make the content resize and keep its aspect ratio, the `` element needs add_theme_support( 'responsive-embeds' ); ``` -## Cover block padding +## Spacing control -Some blocks can provide padding controls in the editor for users. This is off by default, and requires the theme to opt in by declaring support: +Using the Gutenberg plugin (version 8.3 or later), some blocks can provide padding controls in the editor for users. This is off by default, and requires the theme to opt in by declaring support: ```php add_theme_support('custom-spacing'); diff --git a/docs/designers-developers/developers/tutorials/block-based-themes/README.md b/docs/designers-developers/developers/tutorials/block-based-themes/README.md index 5739b34b44229a..a701cd46228814 100644 --- a/docs/designers-developers/developers/tutorials/block-based-themes/README.md +++ b/docs/designers-developers/developers/tutorials/block-based-themes/README.md @@ -15,7 +15,7 @@ This tutorial is up to date as of Gutenberg version 9.1. 1. [What is needed to create a block-based theme?](/docs/designers-developers/developers/tutorials/block-based-themes/README.md#what-is-needed-to-create-a-block-based-theme) 2. [Creating the theme](/docs/designers-developers/developers/tutorials/block-based-themes/README.md#creating-the-theme) 3. [Creating the templates and template parts](/docs/designers-developers/developers/tutorials/block-based-themes/README.md#creating-the-templates-and-template-parts) - 4. [Experimental-theme.json -Global styles](/docs/designers-developers/developers/tutorials/block-based-themes/README.md#experimental-theme-json-global-styles) + 4. [Experimental-theme.json - Global styles](/docs/designers-developers/developers/tutorials/block-based-themes/README.md#experimental-theme-json-global-styles) 5. [Adding blocks](/docs/designers-developers/developers/tutorials/block-based-themes/block-based-themes-2-adding-blocks.md) ## What is needed to create a block-based theme? @@ -34,7 +34,7 @@ A block based theme requires an `index.php` file, an index template file, a `sty The theme may optionally include an [experimental-theme.json file](/docs/designers-developers/developers/themes/theme-json.md) to manage global styles. You decide what additional templates and template parts to include in your theme. -Templates are placed inside the block-templates folder, and template parts are placed inside the block-template-parts folder: +Templates are placed inside the `block-templates` folder, and template parts are placed inside the `block-template-parts` folder: ``` theme @@ -57,7 +57,7 @@ theme ## Creating the theme Create a new folder for your theme in `/wp-content/themes/`. -Inside this folder, create the block-templates and block-template-parts folders. +Inside this folder, create the `block-templates` and `block-template-parts` folders. Create a `style.css` file. The file header in the `style.css` file has [the same items that you would use in a traditional theme](https://developer.wordpress.org/themes/basics/main-stylesheet-style-css/#explanations). @@ -166,7 +166,7 @@ theme ### Creating the templates and template parts -Create two template parts called `footer.html` and `header.html` and place them inside the block-template-parts folder. You can leave the files empty for now. +Create two template parts called `footer.html` and `header.html` and place them inside the `block-template-parts` folder. You can leave the files empty for now. Inside the block-templates folder, create an `index.html` file. @@ -206,7 +206,7 @@ Add the following global presets to the `experimental-theme.json` file: ``` { "global": { - "setttings": { + "settings": { "color": { "palette": [ { @@ -228,7 +228,7 @@ Add the following global presets to the `experimental-theme.json` file: "medium": "2" }, { - large": "2.5" + "large": "2.5" } ] } @@ -318,7 +318,7 @@ Below are the presets and styles combined: ``` { "global": { - "setttings": { + "settings": { "color": { "palette": [ { @@ -340,7 +340,7 @@ Below are the presets and styles combined: "medium": "2" }, { - large": "2.5" + "large": "2.5" } ] } diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md b/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md index b8a4a94996b014..5df98a568fbcdd 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md @@ -8,8 +8,11 @@ The editor will automatically generate a class name for each block type to simpl {% ESNext %} ```jsx import { registerBlockType } from '@wordpress/blocks'; +import { useBlockProps } from '@wordpress/block-editor'; registerBlockType( 'gutenberg-examples/example-02-stylesheets', { + apiVersion: 2, + title: 'Example: Stylesheets', icon: 'universal-access-alt', @@ -18,43 +21,51 @@ registerBlockType( 'gutenberg-examples/example-02-stylesheets', { example: {}, - edit( { className } ) { - return

Hello World, step 2 (from the editor, in green).

; + edit() { + const blockProps = useBlockProps(); + + return

Hello World, step 2 (from the editor, in green).

; }, save() { - return

Hello World, step 2 (from the frontend, in red).

; + const blockProps = useBlockProps.save(); + + return

Hello World, step 2 (from the frontend, in red).

; }, } ); ``` {% ES5 %} ```js -( function( blocks, element ) { +( function( blocks, element, blockEditor ) { var el = element.createElement; blocks.registerBlockType( 'gutenberg-examples/example-02-stylesheets', { + apiVersion: 2, title: 'Example: Stylesheets', icon: 'universal-access-alt', category: 'design', example: {}, edit: function( props ) { + var blockProps = blockEditor.useBlockProps(); return el( 'p', - { className: props.className }, + blockProps, 'Hello World, step 2 (from the editor, in green).' ); }, save: function() { + var blockProps = blockEditor.useBlockProps.save(); return el( 'p', - {}, + blockProps, 'Hello World, step 2 (from the frontend, in red).' ); }, } ); }( window.wp.blocks, - window.wp.element + window.wp.element, + window.wp.blockEditor, ) ); ``` {% end %} @@ -120,6 +131,7 @@ function gutenberg_examples_02_register_block() { ); register_block_type( 'gutenberg-examples/example-02-stylesheets', array( + 'apiVersion' => 2, 'style' => 'gutenberg-examples-02', 'editor_style' => 'gutenberg-examples-02-editor', 'editor_script' => 'gutenberg-examples-02', diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md b/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md index b1798ccde55ca9..fe4dfe67d0f365 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md @@ -16,12 +16,14 @@ You can also customize the toolbar to include controls specific to your block ty import { registerBlockType } from '@wordpress/blocks'; import { + useBlockProps, RichText, AlignmentToolbar, BlockControls, } from '@wordpress/block-editor'; registerBlockType( 'gutenberg-examples/example-04-controls-esnext', { + apiVersion: 2, title: 'Example: Controls (esnext)', icon: 'universal-access-alt', category: 'design', @@ -48,9 +50,10 @@ registerBlockType( 'gutenberg-examples/example-04-controls-esnext', { content, alignment, }, - className, } = props; + const blockProps = useBlockProps(); + const onChangeContent = ( newContent ) => { props.setAttributes( { content: newContent } ); }; @@ -60,7 +63,7 @@ registerBlockType( 'gutenberg-examples/example-04-controls-esnext', { }; return ( -
+
{ { + const blockProps = useBlockProps.save(); + return ( - +
+ +
); }, } ); ``` {% ES5 %} ```js -( function( blocks, editor, element ) { +( function( blocks, blockEditor, element ) { var el = element.createElement; - var RichText = editor.RichText; - var AlignmentToolbar = editor.AlignmentToolbar; - var BlockControls = editor.BlockControls; + var RichText = blockEditor.RichText; + var AlignmentToolbar = blockEditor.AlignmentToolbar; + var BlockControls = blockEditor.BlockControls; + var useBlockProps = blockEditor.useBlockProps; blocks.registerBlockType( 'gutenberg-examples/example-04-controls', { title: 'Example: Controls', @@ -123,6 +131,7 @@ registerBlockType( 'gutenberg-examples/example-04-controls-esnext', { edit: function( props ) { var content = props.attributes.content; var alignment = props.attributes.alignment; + var blockProps = useBlockProps(); function onChangeContent( newContent ) { props.setAttributes( { content: newContent } ); @@ -132,7 +141,9 @@ registerBlockType( 'gutenberg-examples/example-04-controls-esnext', { props.setAttributes( { alignment: newAlignment === undefined ? 'none' : newAlignment } ); } - return [ + return el( + 'div', + blockProps, el( BlockControls, { key: 'controls' }, @@ -150,25 +161,30 @@ registerBlockType( 'gutenberg-examples/example-04-controls-esnext', { key: 'richtext', tagName: 'p', style: { textAlign: alignment }, - className: props.className, onChange: onChangeContent, value: content, } ), - ]; + ); }, save: function( props ) { - return el( RichText.Content, { - tagName: 'p', - className: 'gutenberg-examples-align-' + props.attributes.alignment, - value: props.attributes.content, - } ); + var blockProps = useBlockProps.save(); + + return el( + 'div', + blockProps, + el( RichText.Content, { + tagName: 'p', + className: 'gutenberg-examples-align-' + props.attributes.alignment, + value: props.attributes.content, + } ) + ); }, } ); }( window.wp.blocks, - window.wp.editor, + window.wp.blockEditor, window.wp.element ) ); ``` diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md index da9175642d5456..4f6d2c2557d5f7 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md @@ -22,8 +22,10 @@ The following code example shows how to create a dynamic block that shows only t ```jsx import { registerBlockType } from '@wordpress/blocks'; import { withSelect } from '@wordpress/data'; +import { useBlockProps } from '@wordpress/block-editor'; registerBlockType( 'gutenberg-examples/example-dynamic', { + apiVersion: 2, title: 'Example: last post', icon: 'megaphone', category: 'widgets', @@ -32,31 +34,34 @@ registerBlockType( 'gutenberg-examples/example-dynamic', { return { posts: select( 'core' ).getEntityRecords( 'postType', 'post' ), }; - } )( ( { posts, className } ) => { - if ( ! posts ) { - return 'Loading...'; - } + } )( ( { posts } ) => { + const blockProps = useBlockProps(); - if ( posts && posts.length === 0 ) { - return 'No posts'; - } - - const post = posts[ 0 ]; - - return - { post.title.rendered } - ; + return ( +
+ { ! posts && 'Loading' } + { posts && posts.length === 0 && 'No Posts' } + { posts && posts.length > 0 && ( + + { posts[ 0 ].title.rendered } + + ) } +
+ ) + } ), } ); ``` {% ES5 %} ```js -( function( blocks, element, data ) { +( function( blocks, element, data, blockEditor ) { var el = element.createElement, registerBlockType = blocks.registerBlockType, - withSelect = data.withSelect; + withSelect = data.withSelect, + useBlockProps = blockEditor.useBlockProps; registerBlockType( 'gutenberg-examples/example-dynamic', { + apiVersion: 2, title: 'Example: last post', icon: 'megaphone', category: 'widgets', @@ -65,20 +70,25 @@ registerBlockType( 'gutenberg-examples/example-dynamic', { posts: select( 'core' ).getEntityRecords( 'postType', 'post' ), }; } )( function( props ) { + var blockProps = useBlockProps(); + var content; if ( ! props.posts ) { - return 'Loading...'; - } - - if ( props.posts.length === 0 ) { - return 'No posts'; + content = 'Loading...'; + } else if ( props.posts.length === 0 ) { + content = 'No posts'; + } else { + var post = props.posts[ 0 ]; + content = el( + 'a', + { href: post.link }, + post.title.rendered + ); } - var className = props.className; - var post = props.posts[ 0 ]; - return el( - 'a', - { className: className, href: post.link }, - post.title.rendered + return el( + 'div', + blockProps, + content ); } ), } ); @@ -86,6 +96,7 @@ registerBlockType( 'gutenberg-examples/example-dynamic', { window.wp.blocks, window.wp.element, window.wp.data, + window.wp.blockEditor, ) ); ``` {% end %} @@ -128,6 +139,7 @@ function gutenberg_examples_dynamic() { ); register_block_type( 'gutenberg-examples/example-dynamic', array( + 'apiVersion' => 2, 'editor_script' => 'gutenberg-examples-dynamic', 'render_callback' => 'gutenberg_examples_dynamic_render_callback' ) ); @@ -154,40 +166,52 @@ Gutenberg 2.8 added the [``](/packages/server-side-render/READ ```jsx import { registerBlockType } from '@wordpress/blocks'; import ServerSideRender from '@wordpress/server-side-render'; +import { useBlockProps } from '@wordpress/block-editor'; registerBlockType( 'gutenberg-examples/example-dynamic', { + apiVersion: 2, title: 'Example: last post', icon: 'megaphone', category: 'widgets', edit: function( props ) { + const blockProps = useBlockProps(); return ( - +
+ +
); }, } ); ``` {% ES5 %} ```js -( function( blocks, element, serverSideRender ) { +( function( blocks, element, serverSideRender, blockEditor ) { var el = element.createElement, registerBlockType = blocks.registerBlockType, - ServerSideRender = serverSideRender; + ServerSideRender = serverSideRender, + useBlockProps = blockEditor.useBlockProps; registerBlockType( 'gutenberg-examples/example-dynamic', { + apiVersion: 2, title: 'Example: last post', icon: 'megaphone', category: 'widgets', edit: function( props ) { + var blockProps = useBlockProps(); return ( - el( ServerSideRender, { - block: 'gutenberg-examples/example-dynamic', - attributes: props.attributes, - } ) + el( + 'div', + blockProps, + el( ServerSideRender, { + block: 'gutenberg-examples/example-dynamic', + attributes: props.attributes, + } ) + ) ); }, } ); @@ -195,6 +219,7 @@ registerBlockType( 'gutenberg-examples/example-dynamic', { window.wp.blocks, window.wp.element, window.wp.serverSideRender, + window.wp.blockEditor, ) ); ``` {% end %} diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md b/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md index 0956c6bdd9fa2e..e3696634992eb4 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md @@ -56,9 +56,10 @@ Here is the complete block definition for Example 03. {% ESNext %} ```jsx import { registerBlockType } from '@wordpress/blocks'; -import { RichText } from '@wordpress/block-editor'; +import { useBlockProps, RichText } from '@wordpress/block-editor'; registerBlockType( 'gutenberg-examples/example-03-editable-esnext', { + apiVersion: 2, title: 'Example: Editable (esnext)', icon: 'universal-access-alt', category: 'design', @@ -76,30 +77,34 @@ registerBlockType( 'gutenberg-examples/example-03-editable-esnext', { }, edit: ( props ) => { const { attributes: { content }, setAttributes, className } = props; + const blockProps = useBlockProps(); const onChangeContent = ( newContent ) => { setAttributes( { content: newContent } ); }; return ( ); }, save: ( props ) => { - return ; + const blockProps = useBlockProps.save(); + return ; }, } ); ``` {% ES5 %} ```js -( function( blocks, editor, element ) { +( function( blocks, blockEditor, element ) { var el = element.createElement; - var RichText = editor.RichText; + var RichText = blockEditor.RichText; + var useBlockProps = blockEditor.useBlockProps; blocks.registerBlockType( 'gutenberg-examples/example-03-editable', { + apiVersion: 2, title: 'Example: Editable', icon: 'universal-access-alt', category: 'design', @@ -117,6 +122,7 @@ registerBlockType( 'gutenberg-examples/example-03-editable-esnext', { }, }, edit: function( props ) { + var blockProps = useBlockProps(); var content = props.attributes.content; function onChangeContent( newContent ) { props.setAttributes( { content: newContent } ); @@ -124,24 +130,25 @@ registerBlockType( 'gutenberg-examples/example-03-editable-esnext', { return el( RichText, - { + Object.assign( blockProps, { tagName: 'p', - className: props.className, onChange: onChangeContent, value: content, - } + } ) ); }, save: function( props ) { - return el( RichText.Content, { - tagName: 'p', value: props.attributes.content, - } ); + var blockProps = useBlockProps.save(); + return el( RichText.Content, Object.assign( blockProps, { + tagName: 'p', + value: props.attributes.content, + } ) ); }, } ); }( window.wp.blocks, - window.wp.editor, + window.wp.blockEditor, window.wp.element ) ); ``` diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/nested-blocks-inner-blocks.md b/docs/designers-developers/developers/tutorials/block-tutorial/nested-blocks-inner-blocks.md index ed156a0eb72777..f7f46d6c71bec8 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/nested-blocks-inner-blocks.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/nested-blocks-inner-blocks.md @@ -10,22 +10,26 @@ Here is the basic InnerBlocks usage. {% ESNext %} ```js import { registerBlockType } from '@wordpress/blocks'; -import { InnerBlocks } from '@wordpress/block-editor'; +import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; registerBlockType( 'gutenberg-examples/example-06', { // ... - edit: ( { className } ) => { + edit: () => { + const blockProps = useBlockProps(); + return ( -
+
); }, - save: ( { className } ) => { + save: () => { + const blockProps = useBlockProps.save(); + return ( -
+
); @@ -37,23 +41,28 @@ registerBlockType( 'gutenberg-examples/example-06', { ( function( blocks, element, blockEditor ) { var el = element.createElement; var InnerBlocks = blockEditor.InnerBlocks; + var useBlockProps = blockEditor.useBlockProps; blocks.registerBlockType( 'gutenberg-examples/example-06', { title: 'Example: Inner Blocks', category: 'design', - edit: function( props ) { + edit: function() { + var blockProps = useBlockProps(); + return el( 'div', - { className: props.className }, + blockProps, el( InnerBlocks ) ); }, - save: function( props ) { + save: function() { + var blockProps = useBlockProps.save(); + return el( 'div', - { className: props.className }, + blockProps, el( InnerBlocks.Content ) ); }, diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md b/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md index 6ba06f0981009a..53a73d7c5d7e80 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md @@ -28,6 +28,7 @@ function gutenberg_examples_01_register_block() { ); register_block_type( 'gutenberg-examples/example-01-basic-esnext', array( + 'apiVersion' => 2, 'editor_script' => 'gutenberg-examples-01-esnext', ) ); @@ -51,6 +52,7 @@ With the script enqueued, let's look at the implementation of the block itself: {% ESNext %} ```jsx import { registerBlockType } from '@wordpress/blocks'; +import { useBlockProps } from '@wordpress/block-editor'; const blockStyle = { backgroundColor: '#900', @@ -59,22 +61,28 @@ const blockStyle = { }; registerBlockType( 'gutenberg-examples/example-01-basic-esnext', { + apiVersion: 2, title: 'Example: Basic (esnext)', icon: 'universal-access-alt', category: 'design', example: {}, edit() { - return
Hello World, step 1 (from the editor).
; + const blockProps = useBlockProps( { style: blockStyle } ); + + return
Hello World, step 1 (from the editor).
; }, save() { - return
Hello World, step 1 (from the frontend).
; + const blockProps = useBlockProps.save( { style: blockStyle } ); + + return
Hello World, step 1 (from the frontend).
; }, } ); ``` {% ES5 %} ```js -( function( blocks, element ) { +( function( blocks, element, blockEditor ) { var el = element.createElement; + var useBlockProps = blockEditor.useBlockProps; var blockStyle = { backgroundColor: '#900', @@ -83,28 +91,32 @@ registerBlockType( 'gutenberg-examples/example-01-basic-esnext', { }; blocks.registerBlockType( 'gutenberg-examples/example-01-basic', { + apiVersion: 2, title: 'Example: Basic', icon: 'universal-access-alt', category: 'design', example: {}, edit: function() { + var blockProps = useBlockProps( { style: blockStyle } ); return el( 'p', - { style: blockStyle }, + blockProps, 'Hello World, step 1 (from the editor).' ); }, save: function() { + var blockProps = useBlockProps.save( { style: blockStyle } ); return el( 'p', - { style: blockStyle }, + blockProps, 'Hello World, step 1 (from the frontend).' ); }, } ); }( window.wp.blocks, - window.wp.element + window.wp.element, + window.wp.blockEditor ) ); ``` {% end %} diff --git a/docs/designers-developers/developers/tutorials/create-block/attributes.md b/docs/designers-developers/developers/tutorials/create-block/attributes.md index 50ab92359eaa18..5614987e33fa03 100644 --- a/docs/designers-developers/developers/tutorials/create-block/attributes.md +++ b/docs/designers-developers/developers/tutorials/create-block/attributes.md @@ -16,7 +16,7 @@ attributes: { Add this to the `index.js` file within the `registerBlockType` function. The `attributes` are at the same level as the title and description fields. -When the block loads it will: look at the saved content for the block, look for the div tag, take the text portion, and store the content in an `attributes.message` variable. +When the block loads it will look at the saved content for the block, look for the div tag, take the text portion, and store the content in an `attributes.message` variable. Note: The text portion is equivalent to `innerText` attribute of a DOM element. For more details and other examples see the [Block Attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md). diff --git a/docs/designers-developers/developers/tutorials/create-block/block-anatomy.md b/docs/designers-developers/developers/tutorials/create-block/block-anatomy.md index e078c5e7a2807a..2eea119b98ecd2 100644 --- a/docs/designers-developers/developers/tutorials/create-block/block-anatomy.md +++ b/docs/designers-developers/developers/tutorials/create-block/block-anatomy.md @@ -8,8 +8,10 @@ Here is the complete code for registering a block: ```js import { registerBlockType } from '@wordpress/blocks'; +import { useBlockProps } from '@wordpress/block-editor'; registerBlockType( 'create-block/gutenpride', { + apiVersion: 2, title: 'Gutenpride', description: 'Example block.', category: 'widgets', @@ -20,11 +22,13 @@ registerBlockType( 'create-block/gutenpride', { }, edit: () => { - return
Hello in Editor.
; + const blockProps = useBlockProps(); + return
Hello in Editor.
; }, save: () => { - return
Hello in Save.
; + const blockProps = useBlockProps.save(); + return
Hello in Save.
; }, } ); ``` diff --git a/docs/designers-developers/developers/tutorials/create-block/block-code.md b/docs/designers-developers/developers/tutorials/create-block/block-code.md index d97ccb0863d77b..61a4b920aab95f 100644 --- a/docs/designers-developers/developers/tutorials/create-block/block-code.md +++ b/docs/designers-developers/developers/tutorials/create-block/block-code.md @@ -12,6 +12,7 @@ In the `gutenpride.php` file, the enqueue process is already setup from the gene ```php register_block_type( 'create-block/gutenpride', array( + 'apiVersion' => 2, 'editor_script' => 'create-block-gutenpride-block-editor', 'editor_style' => 'create-block-gutenpride-block-editor', 'style' => 'create-block-gutenpride-block', diff --git a/docs/designers-developers/developers/tutorials/create-block/wp-plugin.md b/docs/designers-developers/developers/tutorials/create-block/wp-plugin.md index e97313bddf6fb6..abfe91bc62ea2e 100644 --- a/docs/designers-developers/developers/tutorials/create-block/wp-plugin.md +++ b/docs/designers-developers/developers/tutorials/create-block/wp-plugin.md @@ -92,7 +92,7 @@ To load the built script, so it is run within the editor, you need to tell WordP ```php function create_block_gutenpride_block_init() { - $dir = dirname( __FILE__ ); + $dir = __DIR__; $script_asset_path = "$dir/build/index.asset.php"; if ( ! file_exists( $script_asset_path ) ) { @@ -127,6 +127,7 @@ function create_block_gutenpride_block_init() { ); register_block_type( 'create-block/gutenpride', array( + 'apiVersion' => 2, 'editor_script' => 'create-block-gutenpride-block-editor', 'editor_style' => 'create-block-gutenpride-block-editor', 'style' => 'create-block-gutenpride-block', diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md index 887f6ffa193b5a..bca448658d2c41 100644 --- a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md +++ b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md @@ -15,13 +15,15 @@ import { registerBlockType } from '@wordpress/blocks'; import { TextControl } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; import { useEntityProp } from '@wordpress/core-data'; +import { useBlockProps } from '@wordpress/block-editor'; registerBlockType( 'myguten/meta-block', { title: 'Meta Block', icon: 'smiley', category: 'text', - edit( { className, setAttributes, attributes } ) { + edit( { setAttributes, attributes } ) { + const blockProps = useBlockProps(); const postType = useSelect( ( select ) => select( 'core/editor' ).getCurrentPostType(), [] @@ -37,7 +39,7 @@ registerBlockType( 'myguten/meta-block', { } return ( -
+
2, 'render_callback' => 'myguten_render_paragraph', ) ); ``` diff --git a/docs/designers-developers/glossary.md b/docs/designers-developers/glossary.md index 5a53c51bf2f5fb..02327c0eb36e01 100644 --- a/docs/designers-developers/glossary.md +++ b/docs/designers-developers/glossary.md @@ -10,6 +10,9 @@
Block
The abstract term used to describe units of markup that, composed together, form the content or layout of a webpage. The idea combines concepts of what in WordPress today we achieve with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience.
+
Block Based Theme
+
A theme built in block forward way that allows Full Site Editing to work. The core of a block-based theme are its block templates and block template parts. To date, block-based theme templates have been HTML files of block markup that map to templates from the standard WordPress template hierarchy.
+
Block categories
These are not a WordPress taxonomy, but instead used internally to sort blocks in the Block Library.
@@ -19,6 +22,12 @@
Block name
A unique identifier for a block type, consisting of a plugin-specific namespace and a short label describing the block's intent. e.g. core/image
+
Block Templates
+
A template is a pre-defined arrangement of blocks, possibly with predefined attributes or placeholder content. You can provide a template for a post type, to give users a starting point when creating a new piece of content, or inside a custom block with the InnerBlocks component. At their core, templates are simply HTML files of block markup that map to templates from the standard WordPress template hierarchy, for example index, single or archive. This helps control the front-end defaults of a site that are not edited via the Page Editor or the Post Editor. See the templates documentation for more information.
+ +
Block Template Parts
+
Building on Block Templates, these parts help set structure for reusable items like a Footer or Header that one typically sees in a WordPress site. They are primarily site structure and are never to be mixed with the post content editor. With Full Site Editing and block based themes, users can create their own arbitrary Template Parts, save those in the database for their site, and re-use them throughout their site. Template parts are equivalent – in blocks – of theme template parts. They are generally defined by a theme first, carry some semantic meaning (could be swapped between themes such as a header), and can only be inserted in the site editor context (within “templates”).
+
Patterns
Patterns are predefined layouts of blocks that can be inserted as starter content that are meant to be changed by the user every time. Once inserted, they exist as a local save and are not global.
@@ -31,6 +40,9 @@
Dynamic block
A type of block where the content of which may change and cannot be determined at the time of saving a post, instead calculated any time the post is shown on the front of a site. These blocks may save fallback content or no content at all in their JavaScript implementation, instead deferring to a PHP block implementation for runtime rendering.
+
Full Site Editing
+
This refers to a collection of features that ultimately allows users to edit their entire website using blocks as the starting point. This feature set includes everything from block patterns to global styles to templates to design tools for blocks (and more). Currently, it's still in an experimental phase.
+
Inspector
Deprecated term. See Settings Sidebar.
@@ -58,10 +70,4 @@
Toolbar
A set of button controls. In the context of a block, usually referring to the toolbar of block controls shown above the selected block.
-
Template
-
A template is a pre-defined arrangement of blocks, possibly with predefined attributes or placeholder content. You can provide a template for a post type, to give users a starting point when creating a new piece of content, or inside a custom block with the InnerBlocks component. See the templates documentation for more information.
- -
Template part
-
Template parts are equivalent – in blocks – of theme template parts. They are generally defined by a theme first. They carry some semantic meaning (could be swapped between themes such as a header) and can only be inserted in the site editor context (within “templates”). They are primarily site structure and are never to be mixed with the post content editor.
- diff --git a/docs/manifest.json b/docs/manifest.json index 5cc645a66cad8e..b22e628e803e72 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -77,6 +77,12 @@ "markdown_source": "../docs/architecture/automated-testing.md", "parent": "architecture" }, + { + "title": "FseTemplates", + "slug": "fse-templates", + "markdown_source": "../docs/architecture/fse-templates.md", + "parent": "architecture" + }, { "title": "Developer Documentation", "slug": "developers", @@ -149,6 +155,12 @@ "markdown_source": "../docs/designers-developers/developers/block-api/block-annotations.md", "parent": "block-api" }, + { + "title": "Block API Versions", + "slug": "versions", + "markdown_source": "../docs/designers-developers/developers/block-api/versions.md", + "parent": "block-api" + }, { "title": "Filter Reference", "slug": "filters", @@ -185,6 +197,12 @@ "markdown_source": "../docs/designers-developers/developers/slotfills/README.md", "parent": "developers" }, + { + "title": "MainDashboardButton", + "slug": "main-dashboard-button", + "markdown_source": "../docs/designers-developers/developers/slotfills/main-dashboard-button.md", + "parent": "slotfills" + }, { "title": "PluginBlockSettingsMenuItem", "slug": "plugin-block-settings-menu-item", diff --git a/docs/toc.json b/docs/toc.json index 7013a98ccc2ca6..3d685561927b4e 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -12,7 +12,8 @@ { "docs/architecture/folder-structure.md": [] }, { "docs/architecture/modularity.md": [] }, { "docs/architecture/performance.md": [] }, - { "docs/architecture/automated-testing.md": [] } + { "docs/architecture/automated-testing.md": [] }, + { "docs/architecture/fse-templates.md": [] } ] }, { "docs/designers-developers/developers/README.md": [ { "docs/designers-developers/developers/block-api/README.md": [ @@ -25,7 +26,8 @@ { "docs/designers-developers/developers/block-api/block-transforms.md": [] }, { "docs/designers-developers/developers/block-api/block-templates.md": [] }, { "docs/designers-developers/developers/block-api/block-patterns.md": [] }, - { "docs/designers-developers/developers/block-api/block-annotations.md": [] } + { "docs/designers-developers/developers/block-api/block-annotations.md": [] }, + { "docs/designers-developers/developers/block-api/versions.md": [] } ] }, { "docs/designers-developers/developers/filters/README.md": [ { "docs/designers-developers/developers/filters/block-filters.md": [] }, @@ -34,6 +36,7 @@ { "docs/designers-developers/developers/filters/autocomplete-filters.md": [] } ] }, {"docs/designers-developers/developers/slotfills/README.md": [ + { "docs/designers-developers/developers/slotfills/main-dashboard-button.md": [] }, { "docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md": [] }, { "docs/designers-developers/developers/slotfills/plugin-document-setting-panel.md": [] }, { "docs/designers-developers/developers/slotfills/plugin-more-menu-item.md": [] }, diff --git a/gutenberg.php b/gutenberg.php index 08e858d39e5875..18ed34191750e7 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the new block editor in core. * Requires at least: 5.3 * Requires PHP: 5.6 - * Version: 9.2.0-rc.1 + * Version: 9.6.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * @@ -65,18 +65,7 @@ function gutenberg_menu() { 'gutenberg_navigation_page' ); } - if ( array_key_exists( 'gutenberg-full-site-editing', get_option( 'gutenberg-experiments' ) ) ) { - add_menu_page( - __( 'Site Editor (beta)', 'gutenberg' ), - __( 'Site Editor (beta)', 'gutenberg' ), - 'edit_theme_options', - 'gutenberg-edit-site', - 'gutenberg_edit_site_page', - 'dashicons-layout' - ); - } } - if ( current_user_can( 'edit_posts' ) ) { add_submenu_page( 'gutenberg', @@ -105,6 +94,31 @@ function gutenberg_menu() { } add_action( 'admin_menu', 'gutenberg_menu', 9 ); +/** + * Site editor's Menu. + * + * Adds a new wp-admin menu item for the Site editor. + * + * @since 9.4.0 + */ +function gutenberg_site_editor_menu() { + if ( gutenberg_is_fse_theme() ) { + add_menu_page( + __( 'Site Editor (beta)', 'gutenberg' ), + sprintf( + /* translators: %s: "beta" label. */ + __( 'Site Editor %s', 'gutenberg' ), + '' . __( 'beta', 'gutenberg' ) . '' + ), + 'edit_theme_options', + 'gutenberg-edit-site', + 'gutenberg_edit_site_page', + 'dashicons-layout' + ); + } +} +add_action( 'admin_menu', 'gutenberg_site_editor_menu', 9 ); + /** * Modify WP admin bar. * @@ -154,7 +168,7 @@ function gutenberg_build_files_notice() { */ function gutenberg_pre_init() { global $wp_version; - if ( defined( 'GUTENBERG_DEVELOPMENT_MODE' ) && GUTENBERG_DEVELOPMENT_MODE && ! file_exists( dirname( __FILE__ ) . '/build/blocks' ) ) { + if ( defined( 'GUTENBERG_DEVELOPMENT_MODE' ) && GUTENBERG_DEVELOPMENT_MODE && ! file_exists( __DIR__ . '/build/blocks' ) ) { add_action( 'admin_notices', 'gutenberg_build_files_notice' ); return; } @@ -170,7 +184,7 @@ function gutenberg_pre_init() { return; } - require_once dirname( __FILE__ ) . '/lib/load.php'; + require_once __DIR__ . '/lib/load.php'; } /** diff --git a/lib/block-supports/border.php b/lib/block-supports/border.php new file mode 100644 index 00000000000000..085cdda187da0c --- /dev/null +++ b/lib/block-supports/border.php @@ -0,0 +1,89 @@ +attributes ) { + $block_type->attributes = array(); + } + + if ( $has_border_radius_support && ! array_key_exists( 'style', $block_type->attributes ) ) { + $block_type->attributes['style'] = array( + 'type' => 'object', + ); + } +} + +/** + * Adds CSS classes and inline styles for border styles to the incoming + * attributes array. This will be applied to the block markup in the front-end. + * + * @param WP_Block_type $block_type Block type. + * @param array $block_attributes Block attributes. + * + * @return array Border CSS classes and inline styles. + */ +function gutenberg_apply_border_support( $block_type, $block_attributes ) { + // Arrays used to ease addition of further border related features in future. + $styles = array(); + + // Border Radius. + if ( gutenberg_has_border_support( $block_type, 'radius' ) ) { + if ( isset( $block_attributes['style']['border']['radius'] ) ) { + $border_radius = intval( $block_attributes['style']['border']['radius'] ); + $styles[] = sprintf( 'border-radius: %dpx;', $border_radius ); + } + } + + // Border width, style etc can be added here. + + // Collect classes and styles. + $attributes = array(); + + if ( ! empty( $styles ) ) { + $attributes['style'] = implode( ' ', $styles ); + } + + return $attributes; +} + +/** + * Checks whether the current block type supports the feature requested. + * + * @param WP_Block_Type $block_type Block type to check for support. + * @param string $feature Name of the feature to check support for. + * @param mixed $default Fallback value for feature support, defaults to false. + * + * @return boolean Whether or not the feature is supported. + */ +function gutenberg_has_border_support( $block_type, $feature, $default = false ) { + $block_support = false; + if ( property_exists( $block_type, 'supports' ) ) { + $block_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalBorder' ), $default ); + } + + return true === $block_support || ( is_array( $block_support ) && gutenberg_experimental_get( $block_support, array( $feature ), false ) ); +} + +// Register the block support. +WP_Block_Supports::get_instance()->register( + 'border', + array( + 'register_attribute' => 'gutenberg_register_border_support', + 'apply' => 'gutenberg_apply_border_support', + ) +); diff --git a/lib/block-supports/generated-classname.php b/lib/block-supports/generated-classname.php index 5f77d3fc90806a..b43260d5135312 100644 --- a/lib/block-supports/generated-classname.php +++ b/lib/block-supports/generated-classname.php @@ -35,11 +35,10 @@ function gutenberg_get_block_default_classname( $block_name ) { * Add the generated classnames to the output. * * @param WP_Block_Type $block_type Block Type. - * @param array $block_attributes Block attributes. * * @return array Block CSS classes and inline styles. */ -function gutenberg_apply_generated_classname_support( $block_type, $block_attributes ) { +function gutenberg_apply_generated_classname_support( $block_type ) { $has_generated_classname_support = true; $attributes = array(); if ( property_exists( $block_type, 'supports' ) ) { diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php index 3f7ebb7c28d21e..29454c43296fd1 100644 --- a/lib/block-supports/typography.php +++ b/lib/block-supports/typography.php @@ -11,21 +11,29 @@ * @param WP_Block_Type $block_type Block Type. */ function gutenberg_register_typography_support( $block_type ) { - $has_font_size_support = false; - if ( property_exists( $block_type, 'supports' ) ) { - $has_font_size_support = gutenberg_experimental_get( $block_type->supports, array( 'fontSize' ), false ); + if ( ! property_exists( $block_type, 'supports' ) ) { + return; } - $has_line_height_support = false; - if ( property_exists( $block_type, 'supports' ) ) { - $has_line_height_support = gutenberg_experimental_get( $block_type->supports, array( 'lineHeight' ), false ); - } + $has_font_size_support = gutenberg_experimental_get( $block_type->supports, array( 'fontSize' ), false ); + $has_font_style_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontStyle' ), false ); + $has_font_weight_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontWeight' ), false ); + $has_line_height_support = gutenberg_experimental_get( $block_type->supports, array( 'lineHeight' ), false ); + $has_text_decoration_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextDecoration' ), false ); + $has_text_transform_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextTransform' ), false ); + + $has_typography_support = $has_font_size_support + || $has_font_weight_support + || $has_font_style_support + || $has_line_height_support + || $has_text_transform_support + || $has_text_decoration_support; if ( ! $block_type->attributes ) { $block_type->attributes = array(); } - if ( ( $has_font_size_support || $has_line_height_support ) && ! array_key_exists( 'style', $block_type->attributes ) ) { + if ( $has_typography_support && ! array_key_exists( 'style', $block_type->attributes ) ) { $block_type->attributes['style'] = array( 'type' => 'object', ); @@ -39,26 +47,30 @@ function gutenberg_register_typography_support( $block_type ) { } /** - * Add CSS classes and inline styles for font sizes to the incoming attributes array. - * This will be applied to the block markup in the front-end. + * Add CSS classes and inline styles for typography features such as font sizes + * to the incoming attributes array. This will be applied to the block markup in + * the front-end. * * @param WP_Block_Type $block_type Block type. * @param array $block_attributes Block attributes. * - * @return array Font size CSS classes and inline styles. + * @return array Typography CSS classes and inline styles. */ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { - $has_font_size_support = false; - $classes = array(); - $styles = array(); - if ( property_exists( $block_type, 'supports' ) ) { - $has_font_size_support = gutenberg_experimental_get( $block_type->supports, array( 'fontSize' ), false ); + if ( ! property_exists( $block_type, 'supports' ) ) { + return array(); } - $has_line_height_support = false; - if ( property_exists( $block_type, 'supports' ) ) { - $has_line_height_support = gutenberg_experimental_get( $block_type->supports, array( 'lineHeight' ), false ); - } + $classes = array(); + $styles = array(); + + $has_font_family_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontFamily' ), false ); + $has_font_style_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontStyle' ), false ); + $has_font_weight_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontWeight' ), false ); + $has_font_size_support = gutenberg_experimental_get( $block_type->supports, array( 'fontSize' ), false ); + $has_line_height_support = gutenberg_experimental_get( $block_type->supports, array( 'lineHeight' ), false ); + $has_text_decoration_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextDecoration' ), false ); + $has_text_transform_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextTransform' ), false ); // Font Size. if ( $has_font_size_support ) { @@ -69,7 +81,42 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { if ( $has_named_font_size ) { $classes[] = sprintf( 'has-%s-font-size', $block_attributes['fontSize'] ); } elseif ( $has_custom_font_size ) { - $styles[] = sprintf( 'font-size: %spx;', $block_attributes['style']['typography']['fontSize'] ); + $styles[] = sprintf( 'font-size: %s;', $block_attributes['style']['typography']['fontSize'] ); + } + } + + // Font Family. + if ( $has_font_family_support ) { + $has_font_family = isset( $block_attributes['style']['typography']['fontFamily'] ); + // Apply required class and style. + if ( $has_font_family ) { + $font_family = $block_attributes['style']['typography']['fontFamily']; + if ( strpos( $font_family, 'var:preset|font-family' ) !== false ) { + // Get the name from the string and add proper styles. + $index_to_splice = strrpos( $font_family, '|' ) + 1; + $font_family_name = substr( $font_family, $index_to_splice ); + $styles[] = sprintf( 'font-family: var(--wp--preset--font-family--%s);', $font_family_name ); + } else { + $styles[] = sprintf( 'font-family: %s;', $block_attributes['style']['color']['fontFamily'] ); + } + } + } + + // Font style. + if ( $has_font_style_support ) { + // Apply font style. + $font_style = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'fontStyle', 'font-style' ); + if ( $font_style ) { + $styles[] = $font_style; + } + } + + // Font weight. + if ( $has_font_weight_support ) { + // Apply font weight. + $font_weight = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'fontWeight', 'font-weight' ); + if ( $font_weight ) { + $styles[] = $font_weight; } } @@ -82,6 +129,22 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { } } + // Text Decoration. + if ( $has_text_decoration_support ) { + $text_decoration_style = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'textDecoration', 'text-decoration' ); + if ( $text_decoration_style ) { + $styles[] = $text_decoration_style; + } + } + + // Text Transform. + if ( $has_text_transform_support ) { + $text_transform_style = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'textTransform', 'text-transform' ); + if ( $text_transform_style ) { + $styles[] = $text_transform_style; + } + } + $attributes = array(); if ( ! empty( $classes ) ) { $attributes['class'] = implode( ' ', $classes ); @@ -93,6 +156,37 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { return $attributes; } +/** + * Generates an inline style for a typography feature e.g. text decoration, + * text transform, and font style. + * + * @param array $attributes Block's attributes. + * @param string $feature Key for the feature within the typography styles. + * @param string $css_property Slug for the CSS property the inline style sets. + * + * @return string CSS inline style. + */ +function gutenberg_typography_get_css_variable_inline_style( $attributes, $feature, $css_property ) { + // Retrieve current attribute value or skip if not found. + $style_value = gutenberg_experimental_get( $attributes, array( 'style', 'typography', $feature ), false ); + if ( ! $style_value ) { + return; + } + + // If we don't have a preset CSS variable, we'll assume it's a regular CSS value. + if ( strpos( $style_value, "var:preset|{$css_property}|" ) === false ) { + return sprintf( '%s:%s;', $css_property, $style_value ); + } + + // We have a preset CSS variable as the style. + // Get the style value from the string and return CSS style. + $index_to_splice = strrpos( $style_value, '|' ) + 1; + $slug = substr( $style_value, $index_to_splice ); + + // Return the actual CSS inline style e.g. `text-decoration:var(--wp--preset--text-decoration--underline);`. + return sprintf( '%s:var(--wp--preset--%s--%s);', $css_property, $css_property, $slug ); +} + // Register the block support. WP_Block_Supports::get_instance()->register( 'typography', diff --git a/lib/blocks.php b/lib/blocks.php index 4245d4cc14c7bc..f4f5a6536cee2a 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -12,12 +12,12 @@ function gutenberg_reregister_core_block_types() { // Blocks directory may not exist if working from a fresh clone. $blocks_dirs = array( - dirname( __FILE__ ) . '/../build/block-library/blocks/' => array( + __DIR__ . '/../build/block-library/blocks/' => array( 'block_folders' => array( 'audio', 'button', 'buttons', - 'classic', + 'freeform', 'code', 'column', 'columns', @@ -49,25 +49,20 @@ function gutenberg_reregister_core_block_types() { ), 'block_names' => array_merge( array( - 'archives.php' => 'core/archives', - 'block.php' => 'core/block', - 'calendar.php' => 'core/calendar', - 'categories.php' => 'core/categories', - 'cover.php' => 'core/cover', - 'latest-comments.php' => 'core/latest-comments', - 'latest-posts.php' => 'core/latest-posts', - 'navigation.php' => 'core/navigation', - 'navigation-link.php' => 'core/navigation-link', - 'rss.php' => 'core/rss', - 'search.php' => 'core/search', - 'shortcode.php' => 'core/shortcode', - 'social-link.php' => 'core/social-link', - 'tag-cloud.php' => 'core/tag-cloud', - ), - ! gutenberg_is_experiment_enabled( 'gutenberg-full-site-editing' ) - ? array() - : - array( + 'archives.php' => 'core/archives', + 'block.php' => 'core/block', + 'calendar.php' => 'core/calendar', + 'categories.php' => 'core/categories', + 'cover.php' => 'core/cover', + 'latest-comments.php' => 'core/latest-comments', + 'latest-posts.php' => 'core/latest-posts', + 'navigation.php' => 'core/navigation', + 'navigation-link.php' => 'core/navigation-link', + 'rss.php' => 'core/rss', + 'search.php' => 'core/search', + 'shortcode.php' => 'core/shortcode', + 'social-link.php' => 'core/social-link', + 'tag-cloud.php' => 'core/tag-cloud', 'post-author.php' => 'core/post-author', 'post-comment.php' => 'core/post-comment', 'post-comment-author.php' => 'core/post-comment-author', @@ -93,7 +88,7 @@ function gutenberg_reregister_core_block_types() { ) ), ), - dirname( __FILE__ ) . '/../build/edit-widgets/blocks/' => array( + __DIR__ . '/../build/edit-widgets/blocks/' => array( 'block_folders' => array( 'legacy-widget', 'widget-area', @@ -105,9 +100,6 @@ function gutenberg_reregister_core_block_types() { ), ); foreach ( $blocks_dirs as $blocks_dir => $details ) { - if ( ! file_exists( $blocks_dir ) ) { - return; - } $block_folders = $details['block_folders']; $block_names = $details['block_names']; @@ -115,9 +107,6 @@ function gutenberg_reregister_core_block_types() { foreach ( $block_folders as $folder_name ) { $block_json_file = $blocks_dir . $folder_name . '/block.json'; - if ( ! file_exists( $block_json_file ) ) { - return; - } // Ideally, all paths to block metadata files should be listed in // WordPress core. In this place we should rather use filter @@ -131,6 +120,7 @@ function gutenberg_reregister_core_block_types() { $registry->unregister( $metadata['name'] ); } + gutenberg_register_core_block_styles( $folder_name ); register_block_type_from_metadata( $block_json_file ); } @@ -143,11 +133,13 @@ function gutenberg_reregister_core_block_types() { if ( $registry->is_registered( $block_names ) ) { $registry->unregister( $block_names ); } + gutenberg_register_core_block_styles( $block_names ); } elseif ( is_array( $block_names ) ) { foreach ( $block_names as $block_name ) { if ( $registry->is_registered( $block_name ) ) { $registry->unregister( $block_name ); } + gutenberg_register_core_block_styles( $block_name ); } } @@ -158,6 +150,46 @@ function gutenberg_reregister_core_block_types() { add_action( 'init', 'gutenberg_reregister_core_block_types' ); +/** + * Registers block styles for a core block. + * + * @param string $block_name The block-name. + * + * @return void + */ +function gutenberg_register_core_block_styles( $block_name ) { + if ( ! gutenberg_should_load_separate_block_styles() ) { + return; + } + + $block_name = str_replace( 'core/', '', $block_name ); + + $style_path = is_rtl() + ? "build/block-library/blocks/$block_name/style-rtl.css" + : "build/block-library/blocks/$block_name/style.css"; + $editor_style_path = is_rtl() + ? "build/block-library/blocks/$block_name/style-editor-rtl.css" + : "build/block-library/blocks/$block_name/style-editor.css"; + + if ( file_exists( gutenberg_dir_path() . $style_path ) ) { + wp_register_style( + 'wp-block-' . $block_name, + gutenberg_url( $style_path ), + array(), + filemtime( gutenberg_dir_path() . $style_path ) + ); + } + + if ( file_exists( gutenberg_dir_path() . $editor_style_path ) ) { + wp_register_style( + 'wp-block-' . $block_name . '-editor', + gutenberg_url( $editor_style_path ), + array(), + filemtime( gutenberg_dir_path() . $editor_style_path ) + ); + } +} + /** * Complements the implementation of block type `core/social-icon`, whether it * be provided by core or the plugin, with derived block types for each diff --git a/lib/class-wp-block-supports.php b/lib/class-wp-block-supports.php index 45b6bd018920a8..c3a9342709eca6 100644 --- a/lib/class-wp-block-supports.php +++ b/lib/class-wp-block-supports.php @@ -88,7 +88,7 @@ public function apply_block_supports() { } $output = array(); - foreach ( $this->block_supports as $name => $block_support_config ) { + foreach ( $this->block_supports as $block_support_config ) { if ( ! isset( $block_support_config['apply'] ) ) { continue; } @@ -127,7 +127,7 @@ private function register_attributes() { $block_type->attributes = array(); } - foreach ( $this->block_supports as $name => $block_support_config ) { + foreach ( $this->block_supports as $block_support_config ) { if ( ! isset( $block_support_config['register_attribute'] ) ) { continue; } @@ -207,9 +207,20 @@ function get_block_wrapper_attributes( $extra_attributes = array() ) { * @return array Block attributes. */ function wp_block_supports_track_block_to_render( $args ) { - if ( null !== $args['render_callback'] ) { + if ( is_callable( $args['render_callback'] ) ) { $block_render_callback = $args['render_callback']; - $args['render_callback'] = function( $attributes, $content, $block ) use ( $block_render_callback ) { + $args['render_callback'] = function( $attributes, $content, $block = null ) use ( $block_render_callback ) { + // Check for null for back compatibility with WP_Block_Type->render + // which is unused since the introduction of WP_Block class. + // + // See: + // - https://core.trac.wordpress.org/ticket/49927 + // - commit 910de8f6890c87f93359c6f2edc6c27b9a3f3292 at wordpress-develop. + + if ( null === $block ) { + return $block_render_callback( $attributes, $content ); + } + $parent_block = WP_Block_Supports::$block_to_render; WP_Block_Supports::$block_to_render = $block->parsed_block; $result = $block_render_callback( $attributes, $content, $block ); diff --git a/lib/class-wp-rest-batch-controller.php b/lib/class-wp-rest-batch-controller.php index 514d9332de2bdf..86a6f11bd60130 100644 --- a/lib/class-wp-rest-batch-controller.php +++ b/lib/class-wp-rest-batch-controller.php @@ -110,7 +110,7 @@ public function serve_batch_request( WP_REST_Request $batch_request ) { $requests[] = $single_request; } - if ( ! method_exists( rest_get_server(), 'match_request_to_handler' ) ) { + if ( ! is_callable( array( rest_get_server(), 'match_request_to_handler' ) ) ) { return $this->polyfill_batching( $requests ); } diff --git a/lib/class-wp-rest-block-types-controller.php b/lib/class-wp-rest-block-types-controller.php index 32b8a17cc45fbd..9d7d819cf70b0a 100644 --- a/lib/class-wp-rest-block-types-controller.php +++ b/lib/class-wp-rest-block-types-controller.php @@ -133,7 +133,7 @@ public function get_items( $request ) { $namespace = $request['namespace']; } - foreach ( $block_types as $slug => $obj ) { + foreach ( $block_types as $obj ) { if ( $namespace ) { $pieces = explode( '/', $obj->name ); $block_namespace = $pieces[0]; diff --git a/lib/class-wp-rest-customizer-nonces.php b/lib/class-wp-rest-customizer-nonces.php index 1eab33cf2ef587..4fc35209ae99de 100644 --- a/lib/class-wp-rest-customizer-nonces.php +++ b/lib/class-wp-rest-customizer-nonces.php @@ -43,10 +43,9 @@ public function register_routes() { /** * Checks if a given request has access to read menu items if they have access to edit them. * - * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ - public function permissions_check( $request ) { + public function permissions_check() { $post_type = get_post_type_object( 'nav_menu_item' ); if ( ! current_user_can( $post_type->cap->edit_posts ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit posts in this post type.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) ); diff --git a/lib/class-wp-rest-widget-types-controller.php b/lib/class-wp-rest-widget-types-controller.php index e39bc84f76ce8b..cf19a273313df7 100644 --- a/lib/class-wp-rest-widget-types-controller.php +++ b/lib/class-wp-rest-widget-types-controller.php @@ -197,7 +197,7 @@ protected function get_widgets() { global $wp_registered_widgets; $widgets = array(); - foreach ( $wp_registered_widgets as $slug => $widget ) { + foreach ( $wp_registered_widgets as $widget ) { $widget_callback = $widget['callback']; unset( $widget['callback'] ); diff --git a/lib/class-wp-theme-json-resolver.php b/lib/class-wp-theme-json-resolver.php new file mode 100644 index 00000000000000..0fc357fb8a7d36 --- /dev/null +++ b/lib/class-wp-theme-json-resolver.php @@ -0,0 +1,352 @@ + __( 'Black', 'gutenberg' ), + 'cyan-bluish-gray' => __( 'Cyan bluish gray', 'gutenberg' ), + 'white' => __( 'White', 'gutenberg' ), + 'pale-pink' => __( 'Pale pink', 'gutenberg' ), + 'vivid-red' => __( 'Vivid red', 'gutenberg' ), + 'luminous-vivid-orange' => __( 'Luminous vivid orange', 'gutenberg' ), + 'luminous-vivid-amber' => __( 'Luminous vivid amber', 'gutenberg' ), + 'light-green-cyan' => __( 'Light green cyan', 'gutenberg' ), + 'vivid-green-cyan' => __( 'Vivid green cyan', 'gutenberg' ), + 'pale-cyan-blue' => __( 'Pale cyan blue', 'gutenberg' ), + 'vivid-cyan-blue' => __( 'Vivid cyan blue', 'gutenberg' ), + 'vivid-purple' => __( 'Vivid purple', 'gutenberg' ), + ); + if ( ! empty( $config['global']['settings']['color']['palette'] ) ) { + foreach ( $config['global']['settings']['color']['palette'] as &$color ) { + $color['name'] = $default_colors_i18n[ $color['slug'] ]; + } + } + + $default_gradients_i18n = array( + 'vivid-cyan-blue-to-vivid-purple' => __( 'Vivid cyan blue to vivid purple', 'gutenberg' ), + 'light-green-cyan-to-vivid-green-cyan' => __( 'Light green cyan to vivid green cyan', 'gutenberg' ), + 'luminous-vivid-amber-to-luminous-vivid-orange' => __( 'Luminous vivid amber to luminous vivid orange', 'gutenberg' ), + 'luminous-vivid-orange-to-vivid-red' => __( 'Luminous vivid orange to vivid red', 'gutenberg' ), + 'very-light-gray-to-cyan-bluish-gray' => __( 'Very light gray to cyan bluish gray', 'gutenberg' ), + 'cool-to-warm-spectrum' => __( 'Cool to warm spectrum', 'gutenberg' ), + 'blush-light-purple' => __( 'Blush light purple', 'gutenberg' ), + 'blush-bordeaux' => __( 'Blush bordeaux', 'gutenberg' ), + 'luminous-dusk' => __( 'Luminous dusk', 'gutenberg' ), + 'pale-ocean' => __( 'Pale ocean', 'gutenberg' ), + 'electric-grass' => __( 'Electric grass', 'gutenberg' ), + 'midnight' => __( 'Midnight', 'gutenberg' ), + ); + if ( ! empty( $config['global']['settings']['color']['gradients'] ) ) { + foreach ( $config['global']['settings']['color']['gradients'] as &$gradient ) { + $gradient['name'] = $default_gradients_i18n[ $gradient['slug'] ]; + } + } + + $default_font_sizes_i18n = array( + 'small' => __( 'Small', 'gutenberg' ), + 'normal' => __( 'Normal', 'gutenberg' ), + 'medium' => __( 'Medium', 'gutenberg' ), + 'large' => __( 'Large', 'gutenberg' ), + 'huge' => __( 'Huge', 'gutenberg' ), + ); + if ( ! empty( $config['global']['settings']['typography']['fontSizes'] ) ) { + foreach ( $config['global']['settings']['typography']['fontSizes'] as &$font_size ) { + $font_size['name'] = $default_font_sizes_i18n[ $font_size['slug'] ]; + } + } + // End i18n logic to remove when JSON i18 strings are extracted. + + self::$core = new WP_Theme_JSON( $config ); + + return self::$core; + } + + /** + * Returns the theme's origin config. + * + * It uses the theme support data if + * the theme hasn't declared any via theme.json. + * + * @param array $theme_support_data Theme support data in theme.json format. + * + * @return WP_Theme_JSON Entity that holds theme data. + */ + private function get_theme_origin( $theme_support_data = array() ) { + $theme_json_data = self::get_from_file( locate_template( 'experimental-theme.json' ) ); + + /* + * We want the presets and settings declared in theme.json + * to override the ones declared via add_theme_support. + */ + $this->theme = new WP_Theme_JSON( $theme_support_data ); + $this->theme->merge( new WP_Theme_JSON( $theme_json_data ) ); + + return $this->theme; + } + + /** + * Returns the CPT that contains the user's origin config + * for the current theme or a void array if none found. + * + * It can also create and return a new draft CPT. + * + * @param bool $should_create_cpt Whether a new CPT should be created if no one was found. + * False by default. + * @param array $post_status_filter Filter CPT by post status. + * ['publish'] by default, so it only fetches published posts. + * + * @return array Custom Post Type for the user's origin config. + */ + private static function get_user_data_from_custom_post_type( $should_create_cpt = false, $post_status_filter = array( 'publish' ) ) { + $user_cpt = array(); + $post_type_filter = 'wp_global_styles'; + $post_name_filter = 'wp-global-styles-' . urlencode( wp_get_theme()->get_stylesheet() ); + $recent_posts = wp_get_recent_posts( + array( + 'numberposts' => 1, + 'orderby' => 'date', + 'order' => 'desc', + 'post_type' => $post_type_filter, + 'post_status' => $post_status_filter, + 'name' => $post_name_filter, + ) + ); + + if ( is_array( $recent_posts ) && ( count( $recent_posts ) === 1 ) ) { + $user_cpt = $recent_posts[0]; + } elseif ( $should_create_cpt ) { + $cpt_post_id = wp_insert_post( + array( + 'post_content' => '{}', + 'post_status' => 'publish', + 'post_type' => $post_type_filter, + 'post_name' => $post_name_filter, + ), + true + ); + $user_cpt = get_post( $cpt_post_id, ARRAY_A ); + } + + return $user_cpt; + } + + /** + * Returns the user's origin config. + * + * @return WP_Theme_JSON Entity that holds user data. + */ + private static function get_user_origin() { + if ( null !== self::$user ) { + return self::$user; + } + + $config = array(); + $user_cpt = self::get_user_data_from_custom_post_type(); + if ( array_key_exists( 'post_content', $user_cpt ) ) { + $decoded_data = json_decode( $user_cpt['post_content'], true ); + + $json_decoding_error = json_last_error(); + if ( JSON_ERROR_NONE !== $json_decoding_error ) { + error_log( 'Error when decoding user schema: ' . json_last_error_msg() ); + return $config; + } + + if ( is_array( $decoded_data ) ) { + $config = $decoded_data; + } + } + self::$user = new WP_Theme_JSON( $config ); + + return self::$user; + } + + /** + * There are three sources of data for a site: + * core, theme, and user. + * + * The main function of the resolver is to + * merge all this data following this algorithm: + * theme overrides core, and user overrides + * data coming from either theme or core. + * + * user data > theme data > core data + * + * The main use case for the resolver is to return + * the merged data up to the user level.However, + * there are situations in which we need the + * data merged up to a different level (theme) + * or no merged at all. + * + * @param array $theme_support_data Existing block editor settings. + * Empty array by default. + * @param string $origin The source of data the consumer wants. + * Valid values are 'core', 'theme', 'user'. + * Default is 'user'. + * @param boolean $merged Whether the data should be merged + * with the previous origins (the default). + * + * @return WP_Theme_JSON + */ + public function get_origin( $theme_support_data = array(), $origin = 'user', $merged = true ) { + + if ( ( 'user' === $origin ) && $merged ) { + $result = new WP_Theme_JSON(); + $result->merge( self::get_core_origin() ); + $result->merge( $this->get_theme_origin( $theme_support_data ) ); + $result->merge( self::get_user_origin() ); + return $result; + } + + if ( ( 'theme' === $origin ) && $merged ) { + $result = new WP_Theme_JSON(); + $result->merge( self::get_core_origin() ); + $result->merge( $this->get_theme_origin( $theme_support_data ) ); + return $result; + } + + if ( 'user' === $origin ) { + return self::get_user_origin(); + } + + if ( 'theme' === $origin ) { + return $this->get_theme_origin( $theme_support_data ); + } + + return self::get_core_origin(); + } + + /** + * Registers a Custom Post Type to store the user's origin config. + */ + public static function register_user_custom_post_type() { + if ( ! gutenberg_experimental_global_styles_has_theme_json_support() ) { + return; + } + + $args = array( + 'label' => __( 'Global Styles', 'gutenberg' ), + 'description' => 'CPT to store user design tokens', + 'public' => false, + 'show_ui' => false, + 'show_in_rest' => true, + 'rest_base' => '__experimental/global-styles', + 'capabilities' => array( + 'read' => 'edit_theme_options', + 'create_posts' => 'edit_theme_options', + 'edit_posts' => 'edit_theme_options', + 'edit_published_posts' => 'edit_theme_options', + 'delete_published_posts' => 'edit_theme_options', + 'edit_others_posts' => 'edit_theme_options', + 'delete_others_posts' => 'edit_theme_options', + ), + 'map_meta_cap' => true, + 'supports' => array( + 'editor', + 'revisions', + ), + ); + register_post_type( 'wp_global_styles', $args ); + } + + /** + * Returns the ID of the custom post type + * that stores user data. + * + * @return integer + */ + public static function get_user_custom_post_type_id() { + if ( null !== self::$user_custom_post_type_id ) { + return self::$user_custom_post_type_id; + } + + $user_cpt = self::get_user_data_from_custom_post_type( true ); + if ( array_key_exists( 'ID', $user_cpt ) ) { + self::$user_custom_post_type_id = $user_cpt['ID']; + } + + return self::$user_custom_post_type_id; + } + +} diff --git a/lib/class-wp-theme-json.php b/lib/class-wp-theme-json.php new file mode 100644 index 00000000000000..8f0071f0478abe --- /dev/null +++ b/lib/class-wp-theme-json.php @@ -0,0 +1,966 @@ + array( + 'color' => array( + 'background' => null, + 'gradient' => null, + 'link' => null, + 'text' => null, + ), + 'spacing' => array( + 'padding' => array( + 'top' => null, + 'right' => null, + 'bottom' => null, + 'left' => null, + ), + ), + 'typography' => array( + 'fontFamily' => null, + 'fontSize' => null, + 'fontStyle' => null, + 'fontWeight' => null, + 'lineHeight' => null, + 'textDecoration' => null, + 'textTransform' => null, + ), + ), + 'settings' => array( + 'border' => array( + 'customRadius' => null, + ), + 'color' => array( + 'custom' => null, + 'customGradient' => null, + 'gradients' => null, + 'link' => null, + 'palette' => null, + ), + 'spacing' => array( + 'customPadding' => null, + 'units' => null, + ), + 'typography' => array( + 'customFontSize' => null, + 'customLineHeight' => null, + 'dropCap' => null, + 'fontFamilies' => null, + 'fontSizes' => null, + 'customFontStyle' => null, + 'customFontWeight' => null, + 'customTextDecorations' => null, + 'customTextTransforms' => null, + ), + 'custom' => null, + ), + ); + + /** + * Presets are a set of values that serve + * to bootstrap some styles: colors, font sizes, etc. + * + * They are a unkeyed array of values such as: + * + * ```php + * array( + * array( + * 'slug' => 'unique-name-within-the-set', + * 'name' => 'Name for the UI', + * => 'value' + * ), + * ) + * ``` + * + * This contains the necessary metadata to process them: + * + * - path => where to find the preset in a theme.json context + * + * - value_key => the key that represents the value + * + * - css_var_infix => infix to use in generating the CSS Custom Property. Example: + * --wp--preset----: + * + * - classes => array containing a structure with the classes to + * generate for the presets. Each class should have + * the class suffix and the property name. Example: + * + * .has-- { + * : + * } + */ + const PRESETS_METADATA = array( + array( + 'path' => array( 'settings', 'color', 'palette' ), + 'value_key' => 'color', + 'css_var_infix' => 'color', + 'classes' => array( + array( + 'class_suffix' => 'color', + 'property_name' => 'color', + ), + array( + 'class_suffix' => 'background-color', + 'property_name' => 'background-color', + ), + ), + ), + array( + 'path' => array( 'settings', 'color', 'gradients' ), + 'value_key' => 'gradient', + 'css_var_infix' => 'gradient', + 'classes' => array( + array( + 'class_suffix' => 'gradient-background', + 'property_name' => 'background', + ), + ), + ), + array( + 'path' => array( 'settings', 'typography', 'fontSizes' ), + 'value_key' => 'size', + 'css_var_infix' => 'font-size', + 'classes' => array( + array( + 'class_suffix' => 'font-size', + 'property_name' => 'font-size', + ), + ), + ), + array( + 'path' => array( 'settings', 'typography', 'fontFamilies' ), + 'value_key' => 'fontFamily', + 'css_var_infix' => 'font-family', + 'classes' => array(), + ), + ); + + /** + * Metadata for style properties. + * + * Each property declares: + * + * - 'value': path to the value in theme.json and block attributes. + * - 'support': path to the block support in block.json. + */ + const PROPERTIES_METADATA = array( + '--wp--style--color--link' => array( + 'value' => array( 'color', 'link' ), + 'support' => array( 'color', 'link' ), + ), + 'background' => array( + 'value' => array( 'color', 'gradient' ), + 'support' => array( 'color', 'gradients' ), + ), + 'backgroundColor' => array( + 'value' => array( 'color', 'background' ), + 'support' => array( 'color' ), + ), + 'borderRadius' => array( + 'value' => array( 'border', 'radius' ), + 'support' => array( '__experimentalBorder' ), + ), + 'color' => array( + 'value' => array( 'color', 'text' ), + 'support' => array( 'color' ), + ), + 'fontFamily' => array( + 'value' => array( 'typography', 'fontFamily' ), + 'support' => array( '__experimentalFontFamily' ), + ), + 'fontSize' => array( + 'value' => array( 'typography', 'fontSize' ), + 'support' => array( 'fontSize' ), + ), + 'fontStyle' => array( + 'value' => array( 'typography', 'fontStyle' ), + 'support' => array( '__experimentalFontStyle' ), + ), + 'fontWeight' => array( + 'value' => array( 'typography', 'fontWeight' ), + 'support' => array( '__experimentalFontWeight' ), + ), + 'lineHeight' => array( + 'value' => array( 'typography', 'lineHeight' ), + 'support' => array( 'lineHeight' ), + ), + 'paddingBottom' => array( + 'value' => array( 'spacing', 'padding', 'bottom' ), + 'support' => array( 'spacing', 'padding' ), + ), + 'paddingLeft' => array( + 'value' => array( 'spacing', 'padding', 'left' ), + 'support' => array( 'spacing', 'padding' ), + ), + 'paddingRight' => array( + 'value' => array( 'spacing', 'padding', 'right' ), + 'support' => array( 'spacing', 'padding' ), + ), + 'paddingTop' => array( + 'value' => array( 'spacing', 'padding', 'top' ), + 'support' => array( 'spacing', 'padding' ), + ), + 'textDecoration' => array( + 'value' => array( 'typography', 'textDecoration' ), + 'support' => array( '__experimentalTextDecoration' ), + ), + 'textTransform' => array( + 'value' => array( 'typography', 'textTransform' ), + 'support' => array( '__experimentalTextTransform' ), + ), + ); + + /** + * Constructor. + * + * @param array $contexts A structure that follows the theme.json schema. + */ + public function __construct( $contexts = array() ) { + $this->contexts = array(); + + if ( ! is_array( $contexts ) ) { + return; + } + + $metadata = $this->get_blocks_metadata(); + foreach ( $contexts as $key => $context ) { + if ( ! isset( $metadata[ $key ] ) ) { + // Skip incoming contexts that can't be found + // within the contexts registered. + continue; + } + + // Filter out top-level keys that aren't valid according to the schema. + $context = array_intersect_key( $context, self::SCHEMA ); + + // Process styles subtree. + $this->process_key( 'styles', $context, self::SCHEMA ); + if ( isset( $context['styles'] ) ) { + $this->process_key( 'color', $context['styles'], self::SCHEMA['styles'] ); + $this->process_key( 'typography', $context['styles'], self::SCHEMA['styles'] ); + + if ( empty( $context['styles'] ) ) { + unset( $context['styles'] ); + } else { + $this->contexts[ $key ]['styles'] = $context['styles']; + } + } + + // Process settings subtree. + $this->process_key( 'settings', $context, self::SCHEMA ); + if ( isset( $context['settings'] ) ) { + $this->process_key( 'color', $context['settings'], self::SCHEMA['settings'] ); + $this->process_key( 'spacing', $context['settings'], self::SCHEMA['settings'] ); + $this->process_key( 'typography', $context['settings'], self::SCHEMA['settings'] ); + + if ( empty( $context['settings'] ) ) { + unset( $context['settings'] ); + } else { + $this->contexts[ $key ]['settings'] = $context['settings']; + } + } + } + } + + /** + * Returns the metadata for each block. + * + * Example: + * + * { + * 'global': { + * 'selector': ':root' + * 'supports': [ 'fontSize', 'backgroundColor' ], + * }, + * 'core/heading/h1': { + * 'selector': 'h1' + * 'supports': [ 'fontSize', 'backgroundColor' ], + * } + * } + * + * @return array Block metadata. + */ + private static function get_blocks_metadata() { + if ( null !== self::$blocks_metadata ) { + return self::$blocks_metadata; + } + + self::$blocks_metadata = array( + self::GLOBAL_NAME => array( + 'selector' => self::GLOBAL_SELECTOR, + 'supports' => self::GLOBAL_SUPPORTS, + ), + ); + + $registry = WP_Block_Type_Registry::get_instance(); + $blocks = $registry->get_all_registered(); + foreach ( $blocks as $block_name => $block_type ) { + /* + * Skips blocks that don't declare support, + * they don't generate styles. + */ + if ( + ! property_exists( $block_type, 'supports' ) || + ! is_array( $block_type->supports ) || + empty( $block_type->supports ) + ) { + continue; + } + + /* + * Extract block support keys that are related to the style properties. + */ + $block_supports = array(); + foreach ( self::PROPERTIES_METADATA as $key => $metadata ) { + if ( gutenberg_experimental_get( $block_type->supports, $metadata['support'] ) ) { + $block_supports[] = $key; + } + } + + /* + * Skip blocks that don't support anything related to styles. + */ + if ( empty( $block_supports ) ) { + continue; + } + + /* + * Assign the selector for the block. + * + * Some blocks can declare multiple selectors: + * + * - core/heading represents the H1-H6 HTML elements + * - core/list represents the UL and OL HTML elements + * - core/group is meant to represent DIV and other HTML elements + * + * Some other blocks don't provide a selector, + * so we generate a class for them based on their name: + * + * - 'core/group' => '.wp-block-group' + * - 'my-custom-library/block-name' => '.wp-block-my-custom-library-block-name' + * + * Note that, for core blocks, we don't add the `core/` prefix to its class name. + * This is for historical reasons, as they come with a class without that infix. + * + */ + if ( + isset( $block_type->supports['__experimentalSelector'] ) && + is_string( $block_type->supports['__experimentalSelector'] ) + ) { + self::$blocks_metadata[ $block_name ] = array( + 'selector' => $block_type->supports['__experimentalSelector'], + 'supports' => $block_supports, + ); + } elseif ( + isset( $block_type->supports['__experimentalSelector'] ) && + is_array( $block_type->supports['__experimentalSelector'] ) + ) { + foreach ( $block_type->supports['__experimentalSelector'] as $key => $selector_metadata ) { + if ( ! isset( $selector_metadata['selector'] ) ) { + continue; + } + + self::$blocks_metadata[ $key ] = array( + 'selector' => $selector_metadata['selector'], + 'supports' => $block_supports, + ); + } + } else { + self::$blocks_metadata[ $block_name ] = array( + 'selector' => '.wp-block-' . str_replace( '/', '-', str_replace( 'core/', '', $block_name ) ), + 'supports' => $block_supports, + ); + } + } + + return self::$blocks_metadata; + } + + /** + * Normalize the subtree according to the given schema. + * This function modifies the given input by removing + * the nodes that aren't valid per the schema. + * + * @param string $key Key of the subtree to normalize. + * @param array $input Whole tree to normalize. + * @param array $schema Schema to use for normalization. + */ + private static function process_key( $key, &$input, $schema ) { + if ( ! isset( $input[ $key ] ) ) { + return; + } + + // Consider valid the input value. + if ( null === $schema[ $key ] ) { + return; + } + + if ( ! is_array( $input[ $key ] ) ) { + unset( $input[ $key ] ); + return; + } + + $input[ $key ] = array_intersect_key( + $input[ $key ], + $schema[ $key ] + ); + + if ( 0 === count( $input[ $key ] ) ) { + unset( $input[ $key ] ); + } + } + + /** + * Given a context, it returns its settings subtree. + * + * @param array $context Context adhering to the theme.json schema. + * + * @return array|null The settings subtree. + */ + private static function extract_settings( $context ) { + if ( empty( $context['settings'] ) ) { + return null; + } + + return $context['settings']; + } + + /** + * Given a tree, it creates a flattened one + * by merging the keys and binding the leaf values + * to the new keys. + * + * It also transforms camelCase names into kebab-case + * and substitutes '/' by '-'. + * + * This is thought to be useful to generate + * CSS Custom Properties from a tree, + * although there's nothing in the implementation + * of this function that requires that format. + * + * For example, assuming the given prefix is '--wp' + * and the token is '--', for this input tree: + * + * { + * 'some/property': 'value', + * 'nestedProperty': { + * 'sub-property': 'value' + * } + * } + * + * it'll return this output: + * + * { + * '--wp--some-property': 'value', + * '--wp--nested-property--sub-property': 'value' + * } + * + * @param array $tree Input tree to process. + * @param string $prefix Prefix to prepend to each variable. '' by default. + * @param string $token Token to use between levels. '--' by default. + * + * @return array The flattened tree. + */ + private static function flatten_tree( $tree, $prefix = '', $token = '--' ) { + $result = array(); + foreach ( $tree as $property => $value ) { + $new_key = $prefix . str_replace( + '/', + '-', + strtolower( preg_replace( '/(? 'property_name', + * 'value' => 'property_value, + * ) + * ``` + * + * Note that this modifies the $declarations in place. + * + * @param array $declarations Holds the existing declarations. + * @param array $context Input context to process. + * @param array $context_supports Supports information for this context. + */ + private static function compute_style_properties( &$declarations, $context, $context_supports ) { + if ( empty( $context['styles'] ) ) { + return; + } + + foreach ( self::PROPERTIES_METADATA as $name => $metadata ) { + if ( ! in_array( $name, $context_supports, true ) ) { + continue; + } + + $value = self::get_property_value( $context['styles'], $metadata['value'] ); + if ( ! empty( $value ) ) { + $kebabcased_name = strtolower( preg_replace( '/(? $kebabcased_name, + 'value' => $value, + ); + } + } + } + + /** + * Given a context, it extracts its presets + * and adds them to the given input $stylesheet. + * + * Note this function modifies $stylesheet in place. + * + * @param string $stylesheet Input stylesheet to add the presets to. + * @param array $context Context to process. + * @param string $selector Selector wrapping the classes. + */ + private static function compute_preset_classes( &$stylesheet, $context, $selector ) { + if ( self::GLOBAL_SELECTOR === $selector ) { + // Classes at the global level do not need any CSS prefixed, + // and we don't want to increase its specificity. + $selector = ''; + } + + foreach ( self::PRESETS_METADATA as $preset ) { + $values = gutenberg_experimental_get( $context, $preset['path'], array() ); + foreach ( $values as $value ) { + foreach ( $preset['classes'] as $class ) { + $stylesheet .= self::to_ruleset( + $selector . '.has-' . $value['slug'] . '-' . $class['class_suffix'], + array( + array( + 'name' => $class['property_name'], + 'value' => $value[ $preset['value_key'] ], + ), + ) + ); + } + } + } + } + + /** + * Given a context, it extracts the CSS Custom Properties + * for the presets and adds them to the $declarations array + * following the format: + * + * ```php + * array( + * 'name' => 'property_name', + * 'value' => 'property_value, + * ) + * ``` + * + * Note that this modifies the $declarations in place. + * + * @param array $declarations Holds the existing declarations. + * @param array $context Input context to process. + */ + private static function compute_preset_vars( &$declarations, $context ) { + foreach ( self::PRESETS_METADATA as $preset ) { + $values = gutenberg_experimental_get( $context, $preset['path'], array() ); + foreach ( $values as $value ) { + $declarations[] = array( + 'name' => '--wp--preset--' . $preset['css_var_infix'] . '--' . $value['slug'], + 'value' => $value[ $preset['value_key'] ], + ); + } + } + } + + /** + * Given a context, it extracts the CSS Custom Properties + * for the custom values and adds them to the $declarations + * array following the format: + * + * ```php + * array( + * 'name' => 'property_name', + * 'value' => 'property_value, + * ) + * ``` + * + * Note that this modifies the $declarations in place. + * + * @param array $declarations Holds the existing declarations. + * @param array $context Input context to process. + */ + private static function compute_theme_vars( &$declarations, $context ) { + $custom_values = gutenberg_experimental_get( $context, array( 'settings', 'custom' ) ); + $css_vars = self::flatten_tree( $custom_values ); + foreach ( $css_vars as $key => $value ) { + $declarations[] = array( + 'name' => '--wp--custom--' . $key, + 'value' => $value, + ); + } + } + + /** + * Given a selector and a declaration list, + * creates the corresponding ruleset. + * + * To help debugging, will add some space + * if SCRIPT_DEBUG is defined and true. + * + * @param string $selector CSS selector. + * @param array $declarations List of declarations. + * + * @return string CSS ruleset. + */ + private static function to_ruleset( $selector, $declarations ) { + if ( empty( $declarations ) ) { + return ''; + } + $ruleset = ''; + + if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { + $declaration_block = array_reduce( + $declarations, + function ( $carry, $element ) { + return $carry .= "\t" . $element['name'] . ': ' . $element['value'] . ";\n"; }, + '' + ); + $ruleset .= $selector . " {\n" . $declaration_block . "}\n"; + } else { + $declaration_block = array_reduce( + $declarations, + function ( $carry, $element ) { + return $carry .= $element['name'] . ': ' . $element['value'] . ';'; }, + '' + ); + $ruleset .= $selector . '{' . $declaration_block . '}'; + } + + return $ruleset; + } + + /** + * Converts each context into a list of rulesets + * to be appended to the stylesheet. + * These rulesets contain all the css variables (custom variables and preset variables). + * + * See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax + * + * For each context this creates a new ruleset such as: + * + * context-selector { + * --wp--preset--category--slug: value; + * --wp--custom--variable: value; + * } + * + * @return string The new stylesheet. + */ + private function get_css_variables() { + $stylesheet = ''; + $metadata = $this->get_blocks_metadata(); + foreach ( $this->contexts as $context_name => $context ) { + if ( empty( $metadata[ $context_name ]['selector'] ) ) { + continue; + } + $selector = $metadata[ $context_name ]['selector']; + + $declarations = array(); + self::compute_preset_vars( $declarations, $context ); + self::compute_theme_vars( $declarations, $context ); + + // Attach the ruleset for style and custom properties. + $stylesheet .= self::to_ruleset( $selector, $declarations ); + } + return $stylesheet; + } + + /** + * Converts each context into a list of rulesets + * containing the block styles to be appended to the stylesheet. + * + * See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax + * + * For each context this creates a new ruleset such as: + * + * context-selector { + * style-property-one: value; + * } + * + * Additionally, it'll also create new rulesets + * as classes for each preset value such as: + * + * .has-value-color { + * color: value; + * } + * + * .has-value-background-color { + * background-color: value; + * } + * + * .has-value-font-size { + * font-size: value; + * } + * + * .has-value-gradient-background { + * background: value; + * } + * + * p.has-value-gradient-background { + * background: value; + * } + * + * @return string The new stylesheet. + */ + private function get_block_styles() { + $stylesheet = ''; + $metadata = $this->get_blocks_metadata(); + foreach ( $this->contexts as $context_name => $context ) { + if ( empty( $metadata[ $context_name ]['selector'] ) || empty( $metadata[ $context_name ]['supports'] ) ) { + continue; + } + $selector = $metadata[ $context_name ]['selector']; + $supports = $metadata[ $context_name ]['supports']; + + $declarations = array(); + self::compute_style_properties( $declarations, $context, $supports ); + + $stylesheet .= self::to_ruleset( $selector, $declarations ); + + // Attach the rulesets for the classes. + self::compute_preset_classes( $stylesheet, $context, $selector ); + } + + return $stylesheet; + } + + /** + * Returns the existing settings for each context. + * + * Example: + * + * { + * 'global': { + * 'color': { + * 'custom': true + * } + * }, + * 'core/paragraph': { + * 'spacing': { + * 'customPadding': true + * } + * } + * } + * + * @return array Settings per context. + */ + public function get_settings() { + return array_filter( + array_map( array( $this, 'extract_settings' ), $this->contexts ), + function ( $element ) { + return null !== $element; + } + ); + } + + /** + * Returns the stylesheet that results of processing + * the theme.json structure this object represents. + * + * @param string $type Type of stylesheet we want accepts 'all', 'block_styles', and 'css_variables'. + * @return string Stylesheet. + */ + public function get_stylesheet( $type = 'all' ) { + switch ( $type ) { + case 'block_styles': + return $this->get_block_styles(); + case 'css_variables': + return $this->get_css_variables(); + default: + return $this->get_css_variables() . $this->get_block_styles(); + } + } + + /** + * Merge new incoming data. + * + * @param WP_Theme_JSON $theme_json Data to merge. + */ + public function merge( $theme_json ) { + $incoming_data = $theme_json->get_raw_data(); + + foreach ( array_keys( $incoming_data ) as $context ) { + foreach ( array( 'settings', 'styles' ) as $subtree ) { + if ( ! isset( $incoming_data[ $context ][ $subtree ] ) ) { + continue; + } + + if ( ! isset( $this->contexts[ $context ][ $subtree ] ) ) { + $this->contexts[ $context ][ $subtree ] = $incoming_data[ $context ][ $subtree ]; + continue; + } + + foreach ( array_keys( self::SCHEMA[ $subtree ] ) as $leaf ) { + if ( ! isset( $incoming_data[ $context ][ $subtree ][ $leaf ] ) ) { + continue; + } + + if ( ! isset( $this->contexts[ $context ][ $subtree ][ $leaf ] ) ) { + $this->contexts[ $context ][ $subtree ][ $leaf ] = $incoming_data[ $context ][ $subtree ][ $leaf ]; + continue; + } + + $this->contexts[ $context ][ $subtree ][ $leaf ] = array_merge( + $this->contexts[ $context ][ $subtree ][ $leaf ], + $incoming_data[ $context ][ $subtree ][ $leaf ] + ); + } + } + } + } + + /** + * Retuns the raw data. + * + * @return array Raw data. + */ + public function get_raw_data() { + return $this->contexts; + } + +} diff --git a/lib/class-wp-widget-block.php b/lib/class-wp-widget-block.php index 0e1bca7335841f..cc6ab5f174e01a 100644 --- a/lib/class-wp-widget-block.php +++ b/lib/class-wp-widget-block.php @@ -54,7 +54,17 @@ public function __construct() { */ public function widget( $args, $instance ) { echo $args['before_widget']; - echo do_blocks( $instance['content'] ); + $content = do_blocks( $instance['content'] ); + + // Handle embeds for block widgets. + // + // When this feature is added to core it may need to be implemented + // differently. WP_Widget_Text is a good reference, that applies a + // filter for its content, which WP_Embed uses in its constructor. + // See https://core.trac.wordpress.org/ticket/51566. + global $wp_embed; + echo $wp_embed->autoembed( $content ); + echo $args['after_widget']; } diff --git a/lib/client-assets.php b/lib/client-assets.php index f1a72422ba00da..47d178667b2ba6 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -18,7 +18,7 @@ * @since 0.1.0 */ function gutenberg_dir_path() { - return plugin_dir_path( dirname( __FILE__ ) ); + return plugin_dir_path( __DIR__ ); } /** @@ -31,7 +31,7 @@ function gutenberg_dir_path() { * @since 0.1.0 */ function gutenberg_url( $path ) { - return plugins_url( $path, dirname( __FILE__ ) ); + return plugins_url( $path, __DIR__ ); } /** @@ -335,12 +335,13 @@ function gutenberg_register_packages_styles( $styles ) { ); $styles->add_data( 'wp-components', 'rtl', 'replace' ); + $block_library_filename = gutenberg_should_load_separate_block_styles() ? 'common' : 'style'; gutenberg_override_style( $styles, 'wp-block-library', - gutenberg_url( 'build/block-library/style.css' ), + gutenberg_url( 'build/block-library/' . $block_library_filename . '.css' ), array(), - filemtime( gutenberg_dir_path() . 'build/block-library/style.css' ) + filemtime( gutenberg_dir_path() . 'build/block-library/' . $block_library_filename . '.css' ) ); $styles->add_data( 'wp-block-library', 'rtl', 'replace' ); @@ -526,15 +527,25 @@ function gutenberg_register_vendor_script( $scripts, $handle, $src, $deps = arra // Determine whether we can write to this file. If not, don't waste // time doing a network request. // @codingStandardsIgnoreStart - $f = @fopen( $full_path, 'a' ); + + $is_writable = is_writable( $full_path ); + if ( $is_writable ) { + $f = @fopen( $full_path, 'a' ); + if ( ! $f ) { + $is_writable = false; + } else { + fclose( $f ); + } + } + // @codingStandardsIgnoreEnd - if ( ! $f ) { + if ( ! $is_writable ) { // Failed to open the file for writing, probably due to server // permissions. Enqueue the script directly from the URL instead. gutenberg_override_script( $scripts, $handle, $src, $deps, $ver, $in_footer ); return; } - fclose( $f ); + $response = wp_remote_get( $src ); if ( wp_remote_retrieve_response_code( $response ) === 200 ) { $f = fopen( $full_path, 'w' ); @@ -639,3 +650,16 @@ function gutenberg_extend_block_editor_settings_with_default_editor_styles( $set return $settings; } add_filter( 'block_editor_settings', 'gutenberg_extend_block_editor_settings_with_default_editor_styles' ); + +/** + * Adds a flag to the editor settings to know whether we're in FSE theme or not. + * + * @param array $settings Default editor settings. + * + * @return array Filtered editor settings. + */ +function gutenberg_extend_block_editor_settings_with_fse_theme_flag( $settings ) { + $settings['isFSETheme'] = gutenberg_is_fse_theme(); + return $settings; +} +add_filter( 'block_editor_settings', 'gutenberg_extend_block_editor_settings_with_fse_theme_flag' ); diff --git a/lib/compat.php b/lib/compat.php index 2c4dfb9033c103..d211a23dc4915c 100644 --- a/lib/compat.php +++ b/lib/compat.php @@ -366,18 +366,11 @@ function gutenberg_replace_default_block_categories( $default_categories ) { } add_filter( 'block_categories', 'gutenberg_replace_default_block_categories' ); -global $current_parsed_block; -$current_parsed_block = array( - 'blockName' => null, - 'attributes' => null, -); - /** * Shim that hooks into `pre_render_block` so as to override `render_block` with * a function that assigns block context. * * The context handling can be removed when plugin support requires WordPress 5.5.0+. - * The global current_parsed_block assignment can be removed when plugin support requires WordPress 5.6.0+. * * @see https://core.trac.wordpress.org/ticket/49927 * @see https://core.trac.wordpress.org/changeset/48243 @@ -388,19 +381,16 @@ function gutenberg_replace_default_block_categories( $default_categories ) { * @return string String of rendered HTML. */ function gutenberg_render_block_with_assigned_block_context( $pre_render, $parsed_block ) { - global $post, $wp_query; - global $current_parsed_block; + $already_supports_context = version_compare( get_bloginfo( 'version' ), '5.5', '>=' ); /* * If a non-null value is provided, a filter has run at an earlier priority * and has already handled custom rendering and should take precedence. */ - if ( null !== $pre_render ) { + if ( null !== $pre_render || $already_supports_context ) { return $pre_render; } - $current_parsed_block = $parsed_block; - $source_block = $parsed_block; /** This filter is documented in src/wp-includes/blocks.php */ @@ -408,47 +398,6 @@ function gutenberg_render_block_with_assigned_block_context( $pre_render, $parse $context = array(); - if ( $post instanceof WP_Post ) { - $context['postId'] = $post->ID; - - /* - * The `postType` context is largely unnecessary server-side, since the - * ID is usually sufficient on its own. That being said, since a block's - * manifest is expected to be shared between the server and the client, - * it should be included to consistently fulfill the expectation. - */ - $context['postType'] = $post->post_type; - } - - if ( isset( $wp_query->tax_query->queried_terms['category'] ) ) { - $context['query'] = array( 'categoryIds' => array() ); - - foreach ( $wp_query->tax_query->queried_terms['category']['terms'] as $category_slug_or_id ) { - $context['query']['categoryIds'][] = 'slug' === $wp_query->tax_query->queried_terms['category']['field'] ? get_cat_ID( $category_slug_or_id ) : $category_slug_or_id; - } - } - - if ( isset( $wp_query->tax_query->queried_terms['post_tag'] ) ) { - if ( isset( $context['query'] ) ) { - $context['query']['tagIds'] = array(); - } else { - $context['query'] = array( 'tagIds' => array() ); - } - - foreach ( $wp_query->tax_query->queried_terms['post_tag']['terms'] as $tag_slug_or_id ) { - $tag_ID = $tag_slug_or_id; - - if ( 'slug' === $wp_query->tax_query->queried_terms['post_tag']['field'] ) { - $tag = get_term_by( 'slug', $tag_slug_or_id, 'post_tag' ); - - if ( $tag ) { - $tag_ID = $tag->term_id; - } - } - $context['query']['tagIds'][] = $tag_ID; - } - } - /** * Filters the default context provided to a rendered block. * @@ -463,6 +412,139 @@ function gutenberg_render_block_with_assigned_block_context( $pre_render, $parse } add_filter( 'pre_render_block', 'gutenberg_render_block_with_assigned_block_context', 9, 2 ); +/** + * Determine if the current theme needs to load separate block styles or not. + * + * @return bool + */ +function gutenberg_should_load_separate_block_styles() { + $load_separate_styles = gutenberg_is_fse_theme(); + /** + * Determine if separate styles will be loaded for blocks on-render or not. + * + * @param bool $load_separate_styles Whether separate styles will be loaded or not. + * + * @return bool + */ + return apply_filters( 'load_separate_block_styles', $load_separate_styles ); +} + +/** + * Remove the `wp_enqueue_registered_block_scripts_and_styles` hook if needed. + * + * @return void + */ +function gutenberg_remove_hook_wp_enqueue_registered_block_scripts_and_styles() { + if ( gutenberg_should_load_separate_block_styles() ) { + /** + * Avoid enqueueing block assets of all registered blocks for all posts, instead + * deferring to block render mechanics to enqueue scripts, thereby ensuring only + * blocks of the content have their assets enqueued. + * + * This can be removed once minimum support for the plugin is outside the range + * of the version associated with closure of the following ticket. + * + * @see https://core.trac.wordpress.org/ticket/50328 + * + * @see WP_Block::render + */ + remove_action( 'enqueue_block_assets', 'wp_enqueue_registered_block_scripts_and_styles' ); + } +} + +add_action( 'init', 'gutenberg_remove_hook_wp_enqueue_registered_block_scripts_and_styles' ); + +/** + * Callback hooked to the register_block_type_args filter. + * + * This hooks into block registration to inject the default context into the block object. + * It can be removed once the default context is added into Core. + * + * @param array $args Block attributes. + * @return array Block attributes. + */ +function gutenberg_inject_default_block_context( $args ) { + if ( is_callable( $args['render_callback'] ) ) { + $block_render_callback = $args['render_callback']; + $args['render_callback'] = function( $attributes, $content, $block = null ) use ( $block_render_callback ) { + global $post, $wp_query; + + // Check for null for back compatibility with WP_Block_Type->render + // which is unused since the introduction of WP_Block class. + // + // See: + // - https://core.trac.wordpress.org/ticket/49927 + // - commit 910de8f6890c87f93359c6f2edc6c27b9a3f3292 at wordpress-develop. + + if ( null === $block ) { + return $block_render_callback( $attributes, $content ); + } + + $registry = WP_Block_Type_Registry::get_instance(); + $block_type = $registry->get_registered( $block->name ); + + // For WordPress versions that don't support the context API. + if ( ! $block->context ) { + $block->context = array(); + } + + // Inject the post context if not done by Core. + $needs_post_id = ! empty( $block_type->uses_context ) && in_array( 'postId', $block_type->uses_context, true ); + if ( $post instanceof WP_Post && $needs_post_id && ! isset( $block->context['postId'] ) && 'wp_template' !== $post->post_type && 'wp_template_part' !== $post->post_type ) { + $block->context['postId'] = $post->ID; + } + $needs_post_type = ! empty( $block_type->uses_context ) && in_array( 'postType', $block_type->uses_context, true ); + if ( $post instanceof WP_Post && $needs_post_type && ! isset( $block->context['postType'] ) && 'wp_template' !== $post->post_type && 'wp_template_part' !== $post->post_type ) { + /* + * The `postType` context is largely unnecessary server-side, since the + * ID is usually sufficient on its own. That being said, since a block's + * manifest is expected to be shared between the server and the client, + * it should be included to consistently fulfill the expectation. + */ + $block->context['postType'] = $post->post_type; + } + + // Inject the query context if not done by Core. + $needs_query = ! empty( $block_type->uses_context ) && in_array( 'query', $block_type->uses_context, true ); + if ( ! isset( $block->context['query'] ) && $needs_query ) { + if ( isset( $wp_query->tax_query->queried_terms['category'] ) ) { + $block->context['query'] = array( 'categoryIds' => array() ); + + foreach ( $wp_query->tax_query->queried_terms['category']['terms'] as $category_slug_or_id ) { + $block->context['query']['categoryIds'][] = 'slug' === $wp_query->tax_query->queried_terms['category']['field'] ? get_cat_ID( $category_slug_or_id ) : $category_slug_or_id; + } + } + + if ( isset( $wp_query->tax_query->queried_terms['post_tag'] ) ) { + if ( isset( $block->context['query'] ) ) { + $block->context['query']['tagIds'] = array(); + } else { + $block->context['query'] = array( 'tagIds' => array() ); + } + + foreach ( $wp_query->tax_query->queried_terms['post_tag']['terms'] as $tag_slug_or_id ) { + $tag_ID = $tag_slug_or_id; + + if ( 'slug' === $wp_query->tax_query->queried_terms['post_tag']['field'] ) { + $tag = get_term_by( 'slug', $tag_slug_or_id, 'post_tag' ); + + if ( $tag ) { + $tag_ID = $tag->term_id; + } + } + $block->context['query']['tagIds'][] = $tag_ID; + } + } + } + + return $block_render_callback( $attributes, $content, $block ); + }; + } + return $args; +} + +add_filter( 'register_block_type_args', 'gutenberg_inject_default_block_context' ); + /** * Amends the paths to preload when initializing edit post. * diff --git a/lib/demo-block-template-parts/header.html b/lib/demo-block-template-parts/header.html deleted file mode 100644 index e4abb0f058ae23..00000000000000 --- a/lib/demo-block-template-parts/header.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/lib/demo-block-templates/category.html b/lib/demo-block-templates/category.html deleted file mode 100644 index 3737864c6acb8d..00000000000000 --- a/lib/demo-block-templates/category.html +++ /dev/null @@ -1,20 +0,0 @@ - -
-
- -

- Category Template -

- -
-
- - - - - - - diff --git a/lib/demo-block-templates/front-page.html b/lib/demo-block-templates/front-page.html deleted file mode 100644 index f150777c425f4a..00000000000000 --- a/lib/demo-block-templates/front-page.html +++ /dev/null @@ -1,19 +0,0 @@ - - - -
-

Say Hello to the New New Editor

- - - -

Now you can edit your entire site!

-
- - - - - - - -

Rebuilding the entire editing experience pages and posts was just the start. We have expanded what is possible within the editor by making every part of your site customizable right in the editor. Customize your site navigation. Change your site title. Customize your footer. If you can imagine it, you can build it.

- diff --git a/lib/demo-block-templates/index.html b/lib/demo-block-templates/index.html deleted file mode 100644 index e6d07a97d37e3e..00000000000000 --- a/lib/demo-block-templates/index.html +++ /dev/null @@ -1,12 +0,0 @@ - -
-
- -
-
- - - diff --git a/lib/edit-site-export.php b/lib/edit-site-export.php index c62fb42c2c930c..14c0a756c57b3a 100644 --- a/lib/edit-site-export.php +++ b/lib/edit-site-export.php @@ -10,6 +10,11 @@ * and template parts from the site editor, and close the connection. */ function gutenberg_edit_site_export() { + // Theme templates and template parts need to be synchronized + // before the export. + _gutenberg_synchronize_theme_templates( 'template-part' ); + _gutenberg_synchronize_theme_templates( 'template' ); + // Create ZIP file and directories. $filename = tempnam( get_temp_dir(), 'edit-site-export' ); $zip = new ZipArchive(); @@ -18,23 +23,54 @@ function gutenberg_edit_site_export() { $zip->addEmptyDir( 'theme/block-templates' ); $zip->addEmptyDir( 'theme/block-template-parts' ); - // Load files into ZIP file. - foreach ( get_template_types() as $template_type ) { - // Skip 'embed' for now because it is not a regular template type. - // Skip 'index' because it's a fallback that we handle differently. - if ( in_array( $template_type, array( 'embed', 'index' ), true ) ) { - continue; - } + $theme = wp_get_theme()->get_stylesheet(); - $current_template = gutenberg_find_template_post_and_parts( $template_type ); - if ( isset( $current_template ) ) { - $zip->addFromString( 'theme/block-templates/' . $current_template['template_post']->post_name . '.html', $current_template['template_post']->post_content ); + // Load templates into the zip file. + $template_query = new WP_Query( + array( + 'post_type' => 'wp_template', + 'post_status' => array( 'publish', 'auto-draft' ), + 'tax_query' => array( + array( + 'taxonomy' => 'wp_theme', + 'field' => 'slug', + 'terms' => $theme, + ), + ), + 'posts_per_page' => -1, + 'no_found_rows' => true, + ) + ); + while ( $template_query->have_posts() ) { + $template = $template_query->next_post(); + $zip->addFromString( + 'theme/block-templates/' . $template->post_name . '.html', + gutenberg_strip_post_ids_from_template_part_blocks( $template->post_content ) + ); + } - foreach ( $current_template['template_part_ids'] as $template_part_id ) { - $template_part = get_post( $template_part_id ); - $zip->addFromString( 'theme/block-template-parts/' . $template_part->post_name . '.html', $template_part->post_content ); - } - } + // Load template parts into the zip file. + $template_part_query = new WP_Query( + array( + 'post_type' => 'wp_template_part', + 'post_status' => array( 'publish', 'auto-draft' ), + 'tax_query' => array( + array( + 'taxonomy' => 'wp_theme', + 'field' => 'slug', + 'terms' => $theme, + ), + ), + 'posts_per_page' => -1, + 'no_found_rows' => true, + ) + ); + while ( $template_part_query->have_posts() ) { + $template_part = $template_part_query->next_post(); + $zip->addFromString( + 'theme/block-template-parts/' . $template_part->post_name . '.html', + gutenberg_strip_post_ids_from_template_part_blocks( $template_part->post_content ) + ); } // Send back the ZIP file. @@ -46,6 +82,7 @@ function gutenberg_edit_site_export() { echo readfile( $filename ); die(); } + add_action( 'rest_api_init', function () { @@ -62,3 +99,29 @@ function () { ); } ); + +/** + * Remove post id attributes from template part blocks. + * + * This is needed so that Gutenberg loads the HTML file of the template, instead of looking for a template part post. + * + * @param string $template_content Template content to modify. + * + * @return string Potentially modified template content. + */ +function gutenberg_strip_post_ids_from_template_part_blocks( $template_content ) { + $blocks = parse_blocks( $template_content ); + + array_walk( + $blocks, + function( &$block ) { + if ( 'core/template-part' !== $block['blockName'] ) { + return; + } + + unset( $block['attrs']['postId'] ); + } + ); + + return serialize_blocks( $blocks ); +} diff --git a/lib/edit-site-page.php b/lib/edit-site-page.php index 83089981f59756..bc683ff0c02097 100644 --- a/lib/edit-site-page.php +++ b/lib/edit-site-page.php @@ -87,7 +87,7 @@ function gutenberg_get_editor_styles() { * @param string $hook Page. */ function gutenberg_edit_site_init( $hook ) { - global $current_screen; + global $current_screen, $post; if ( ! gutenberg_is_edit_site_page( $hook ) ) { return; @@ -101,46 +101,18 @@ function gutenberg_edit_site_init( $hook ) { */ $current_screen->is_block_editor( true ); - // Get editor settings. - $max_upload_size = wp_max_upload_size(); - if ( ! $max_upload_size ) { - $max_upload_size = 0; - } - - // This filter is documented in wp-admin/includes/media.php. - $image_size_names = apply_filters( - 'image_size_names_choose', + $settings = array_merge( + gutenberg_get_common_block_editor_settings(), array( - 'thumbnail' => __( 'Thumbnail', 'gutenberg' ), - 'medium' => __( 'Medium', 'gutenberg' ), - 'large' => __( 'Large', 'gutenberg' ), - 'full' => __( 'Full Size', 'gutenberg' ), + 'alignWide' => get_theme_support( 'align-wide' ), + 'siteUrl' => site_url(), + 'postsPerPage' => get_option( 'posts_per_page' ), + 'styles' => gutenberg_get_editor_styles(), + 'defaultTemplateTypes' => gutenberg_get_indexed_default_template_types(), ) ); - $available_image_sizes = array(); - foreach ( $image_size_names as $image_size_slug => $image_size_name ) { - $available_image_sizes[] = array( - 'slug' => $image_size_slug, - 'name' => $image_size_name, - ); - } - - $settings = array( - 'alignWide' => get_theme_support( 'align-wide' ), - 'imageSizes' => $available_image_sizes, - 'isRTL' => is_rtl(), - 'maxUploadFileSize' => $max_upload_size, - 'siteUrl' => site_url(), - 'postsPerPage' => get_option( 'posts_per_page' ), - ); - - $settings['styles'] = gutenberg_get_editor_styles(); - $settings = gutenberg_experimental_global_styles_settings( $settings ); - - // This is so other parts of the code can hook their own settings. - // Example: Global Styles. - global $post; - $settings = apply_filters( 'block_editor_settings', $settings, $post ); + $settings = gutenberg_experimental_global_styles_settings( $settings ); + $settings = gutenberg_extend_settings_block_patterns( $settings ); // Preload block editor paths. // most of these are copied from edit-forms-blocks.php. @@ -170,7 +142,7 @@ function gutenberg_edit_site_init( $hook ) { 'wp.domReady( function() { wp.editSite.initialize( "edit-site-editor", %s ); } );', - wp_json_encode( gutenberg_experiments_editor_settings( $settings ) ) + wp_json_encode( $settings ) ) ); @@ -211,7 +183,7 @@ function gutenberg_edit_site_init( $hook ) { */ function register_site_editor_homepage_settings() { register_setting( - 'general', + 'reading', 'show_on_front', array( 'show_in_rest' => true, @@ -221,7 +193,7 @@ function register_site_editor_homepage_settings() { ); register_setting( - 'general', + 'reading', 'page_on_front', array( 'show_in_rest' => true, diff --git a/lib/editor-settings.php b/lib/editor-settings.php new file mode 100644 index 00000000000000..e0970e761bb490 --- /dev/null +++ b/lib/editor-settings.php @@ -0,0 +1,82 @@ + __( 'Thumbnail', 'gutenberg' ), + 'medium' => __( 'Medium', 'gutenberg' ), + 'large' => __( 'Large', 'gutenberg' ), + 'full' => __( 'Full Size', 'gutenberg' ), + ) + ); + foreach ( $image_size_names as $image_size_slug => $image_size_name ) { + $available_image_sizes[] = array( + 'slug' => $image_size_slug, + 'name' => $image_size_name, + ); + }; + + $settings = array( + '__unstableEnableFullSiteEditingBlocks' => gutenberg_is_fse_theme(), + 'disableCustomColors' => get_theme_support( 'disable-custom-colors' ), + 'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ), + 'disableCustomGradients' => get_theme_support( 'disable-custom-gradients' ), + 'enableCustomLineHeight' => get_theme_support( 'custom-line-height' ), + 'enableCustomUnits' => get_theme_support( 'custom-units' ), + 'imageSizes' => $available_image_sizes, + 'isRTL' => is_rtl(), + 'maxUploadFileSize' => $max_upload_size, + ); + + $color_palette = current( (array) get_theme_support( 'editor-color-palette' ) ); + if ( false !== $color_palette ) { + $settings['colors'] = $color_palette; + } + + $font_sizes = current( (array) get_theme_support( 'editor-font-sizes' ) ); + if ( false !== $font_sizes ) { + $settings['fontSizes'] = $font_sizes; + } + + $gradient_presets = current( (array) get_theme_support( 'editor-gradient-presets' ) ); + if ( false !== $gradient_presets ) { + $settings['gradients'] = $gradient_presets; + } + + return $settings; +} + +/** + * Extends the block editor with settings that are only in the plugin. + * + * @param array $settings Existing editor settings. + * + * @return array Filtered settings. + */ +function gutenberg_extend_post_editor_settings( $settings ) { + $settings['__unstableEnableFullSiteEditingBlocks'] = gutenberg_is_fse_theme(); + return $settings; +} +add_filter( 'block_editor_settings', 'gutenberg_extend_post_editor_settings' ); diff --git a/lib/experimental-default-theme.json b/lib/experimental-default-theme.json index 28a20292ff3f88..15904819cfd7f5 100644 --- a/lib/experimental-default-theme.json +++ b/lib/experimental-default-theme.json @@ -134,37 +134,44 @@ "dropCap": true, "customFontSize": true, "customLineHeight": false, + "customFontStyle": true, + "customFontWeight": true, + "customTextTransforms": true, + "customTextDecorations": true, "fontSizes": [ { "name": "Small", "slug": "small", - "size": 13 + "size": "13px" }, { "name": "Normal", "slug": "normal", - "size": 16 + "size": "16px" }, { "name": "Medium", "slug": "medium", - "size": 20 + "size": "20px" }, { "name": "Large", "slug": "large", - "size": 36 + "size": "36px" }, { "name": "Huge", "slug": "huge", - "size": 42 + "size": "42px" } ] }, "spacing": { "customPadding": false, "units": [ "px", "em", "rem", "vh", "vw" ] + }, + "border": { + "customRadius": true } } } diff --git a/lib/experiments-page.php b/lib/experiments-page.php index 26cb98036b1614..fa35fa81ddfdc8 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -51,28 +51,6 @@ function gutenberg_initialize_experiments_settings() { 'id' => 'gutenberg-navigation', ) ); - add_settings_field( - 'gutenberg-full-site-editing', - __( 'Full Site Editing', 'gutenberg' ), - 'gutenberg_display_experiment_field', - 'gutenberg-experiments', - 'gutenberg_experiments_section', - array( - 'label' => __( 'Enable Full Site Editing (Warning: this will replace your theme and cause potentially irreversible changes to your site. We recommend using this only in a development environment.)', 'gutenberg' ), - 'id' => 'gutenberg-full-site-editing', - ) - ); - add_settings_field( - 'gutenberg-full-site-editing-demo', - __( 'Full Site Editing Demo Templates', 'gutenberg' ), - 'gutenberg_display_experiment_field', - 'gutenberg-experiments', - 'gutenberg_experiments_section', - array( - 'label' => __( 'Enable Full Site Editing demo templates', 'gutenberg' ), - 'id' => 'gutenberg-full-site-editing-demo', - ) - ); register_setting( 'gutenberg-experiments', 'gutenberg-experiments' @@ -110,25 +88,3 @@ function gutenberg_display_experiment_section() { gutenberg_is_experiment_enabled( 'gutenberg-full-site-editing' ), - '__experimentalEnableFullSiteEditingDemo' => gutenberg_is_experiment_enabled( 'gutenberg-full-site-editing-demo' ), - ); - - $gradient_presets = current( (array) get_theme_support( 'editor-gradient-presets' ) ); - if ( false !== $gradient_presets ) { - $experiments_settings['gradients'] = $gradient_presets; - } - - return array_merge( $settings, $experiments_settings ); -} -add_filter( 'block_editor_settings', 'gutenberg_experiments_editor_settings' ); diff --git a/lib/full-site-editing.php b/lib/full-site-editing.php new file mode 100644 index 00000000000000..059b39ccb971f0 --- /dev/null +++ b/lib/full-site-editing.php @@ -0,0 +1,123 @@ + +
+

+
+ $menu_item ) { + if ( + false !== strpos( $menu_item[2], 'customize.php' ) || + false !== strpos( $menu_item[2], 'gutenberg-widgets' ) + ) { + $indexes_to_remove[] = $index; + } + } + + foreach ( $indexes_to_remove as $index ) { + unset( $submenu['themes.php'][ $index ] ); + } + } +} + +add_action( 'admin_menu', 'gutenberg_remove_legacy_pages' ); + +/** + * Removes legacy adminbar items from FSE themes. + * + * @param WP_Admin_Bar $wp_admin_bar The admin-bar instance. + */ +function gutenberg_adminbar_items( $wp_admin_bar ) { + + // Early exit if not an FSE theme. + if ( ! gutenberg_is_fse_theme() ) { + return; + } + + // Remove customizer link. + $wp_admin_bar->remove_node( 'customize' ); + $wp_admin_bar->remove_node( 'customize-background' ); + $wp_admin_bar->remove_node( 'customize-header' ); + $wp_admin_bar->remove_node( 'widgets' ); + + // Add site-editor link. + if ( ! is_admin() && current_user_can( 'edit_theme_options' ) ) { + $wp_admin_bar->add_node( + array( + 'id' => 'site-editor', + 'title' => __( 'Edit site', 'gutenberg' ), + 'href' => admin_url( 'admin.php?page=gutenberg-edit-site' ), + ) + ); + } +} + +add_action( 'admin_bar_menu', 'gutenberg_adminbar_items', 50 ); + +/** + * Activates the 'menu_order' filter and then hooks into 'menu_order' + */ +add_filter( 'custom_menu_order', '__return_true' ); +add_filter( 'menu_order', 'gutenberg_menu_order' ); + +/** + * Filters WordPress default menu order + * + * @param array $menu_order Menu Order. + */ +function gutenberg_menu_order( $menu_order ) { + if ( ! gutenberg_is_fse_theme() ) { + return $menu_order; + } + + $new_positions = array( + // Position the site editor before the appearnce menu. + 'gutenberg-edit-site' => array_search( 'themes.php', $menu_order, true ), + ); + + // Traverse through the new positions and move + // the items if found in the original menu_positions. + foreach ( $new_positions as $value => $new_index ) { + $current_index = array_search( $value, $menu_order, true ); + if ( $current_index ) { + $out = array_splice( $menu_order, $current_index, 1 ); + array_splice( $menu_order, $new_index, 0, $out ); + } + } + return $menu_order; +} diff --git a/lib/full-site-editing/default-template-types.php b/lib/full-site-editing/default-template-types.php new file mode 100644 index 00000000000000..54cc722be96f37 --- /dev/null +++ b/lib/full-site-editing/default-template-types.php @@ -0,0 +1,128 @@ + array( + 'title' => _x( 'Index', 'Template name', 'gutenberg' ), + 'description' => __( 'The default template which is used when no other template can be found', 'gutenberg' ), + ), + 'home' => array( + 'title' => _x( 'Home', 'Template name', 'gutenberg' ), + 'description' => __( 'The home page template, which is the front page by default. If you use a static front page this is the template for the page with the latest posts', 'gutenberg' ), + ), + 'front-page' => array( + 'title' => _x( 'Front Page', 'Template name', 'gutenberg' ), + 'description' => __( 'Used when the site home page is queried', 'gutenberg' ), + ), + 'singular' => array( + 'title' => _x( 'Singular', 'Template name', 'gutenberg' ), + 'description' => __( 'Used when a single entry is queried. This template will be overridden the Single, Post, and Page templates where appropriate', 'gutenberg' ), + ), + 'single' => array( + 'title' => _x( 'Single', 'Template name', 'gutenberg' ), + 'description' => __( 'Used when a single entry that is not a Page is queried', 'gutenberg' ), + ), + 'single-post' => array( + 'title' => _x( 'Post', 'Template name', 'gutenberg' ), + 'description' => __( 'Used when a single Post is queried', 'gutenberg' ), + ), + 'page' => array( + 'title' => _x( 'Page', 'Template name', 'gutenberg' ), + 'description' => __( 'Used when an individual Page is queried', 'gutenberg' ), + ), + 'archive' => array( + 'title' => _x( 'Archive', 'Template name', 'gutenberg' ), + 'description' => __( 'Used when multiple entries are queried. This template will be overridden the Category, Author, and Date templates where appropriate', 'gutenberg' ), + ), + 'author' => array( + 'title' => _x( 'Author Archive', 'Template name', 'gutenberg' ), + 'description' => __( 'Used when a list of Posts from a single author is queried', 'gutenberg' ), + ), + 'category' => array( + 'title' => _x( 'Post Category Archive', 'Template name', 'gutenberg' ), + 'description' => __( 'Used when a list of Posts from a category is queried', 'gutenberg' ), + ), + 'taxonomy' => array( + 'title' => _x( 'Taxonomy Archive', 'Template name', 'gutenberg' ), + 'description' => __( 'Used when a list of posts from a taxonomy is queried', 'gutenberg' ), + ), + 'date' => array( + 'title' => _x( 'Date Archive', 'Template name', 'gutenberg' ), + 'description' => __( 'Used when a list of Posts from a certain date are queried', 'gutenberg' ), + ), + 'tag' => array( + 'title' => _x( 'Tag Archive', 'Template name', 'gutenberg' ), + 'description' => __( 'Used when a list of Posts with a certain tag is queried', 'gutenberg' ), + ), + 'attachment' => array( + 'title' => __( 'Media', 'gutenberg' ), + 'description' => __( 'Used when a Media entry is queried', 'gutenberg' ), + ), + 'search' => array( + 'title' => _x( 'Search Results', 'Template name', 'gutenberg' ), + 'description' => __( 'Used when a visitor searches the site', 'gutenberg' ), + ), + 'privacy-policy' => array( + 'title' => __( 'Privacy Policy', 'gutenberg' ), + 'description' => '', + ), + '404' => array( + 'title' => _x( '404', 'Template name', 'gutenberg' ), + 'description' => __( 'Used when the queried content cannot be found', 'gutenberg' ), + ), + ); + + /** + * Filters the list of template types. + * + * @param array $default_template_types An array of template types, formatted as [ slug => [ title, description ] ]. + * + * @since 5.x.x + */ + return apply_filters( 'default_template_types', $default_template_types ); +} + +/** + * Converts the default template types array from associative to indexed, + * to be used in JS, where numeric keys (e.g. '404') always appear before alphabetical + * ones, regardless of the actual order of the array. + * + * From slug-keyed associative array: + * `[ 'index' => [ 'title' => 'Index', 'description' => 'Index template' ] ]` + * + * ...to indexed array with slug as property: + * `[ [ 'slug' => 'index', 'title' => 'Index', 'description' => 'Index template' ] ]` + * + * @return array The default template types as an indexed array. + */ +function gutenberg_get_indexed_default_template_types() { + $indexed_template_types = array(); + $default_template_types = gutenberg_get_default_template_types(); + foreach ( $default_template_types as $slug => $template_type ) { + $template_type['slug'] = (string) $slug; + $indexed_template_types[] = $template_type; + } + return $indexed_template_types; +} + +/** + * Return a list of all overrideable default template type slugs. + * + * @see get_query_template + * + * @return string[] List of all overrideable default template type slugs. + */ +function gutenberg_get_template_type_slugs() { + return array_keys( gutenberg_get_default_template_types() ); +} diff --git a/lib/full-site-editing/templates-utils.php b/lib/full-site-editing/templates-utils.php new file mode 100644 index 00000000000000..5430470cfa640a --- /dev/null +++ b/lib/full-site-editing/templates-utils.php @@ -0,0 +1,121 @@ +post_name ); + return; + } + + if ( 'description' === $column_name && has_excerpt( $post_id ) ) { + the_excerpt( $post_id ); + return; + } + + if ( 'status' === $column_name ) { + $post_status = get_post_status( $post_id ); + // The auto-draft status doesn't have localized labels. + if ( 'auto-draft' === $post_status ) { + echo esc_html_x( 'Auto-Draft', 'Post status', 'gutenberg' ); + return; + } + $post_status_object = get_post_status_object( $post_status ); + echo esc_html( $post_status_object->label ); + return; + } + + if ( 'theme' === $column_name ) { + $terms = get_the_terms( $post_id, 'wp_theme' ); + if ( empty( $terms ) || is_wp_error( $terms ) ) { + return; + } + $themes = array(); + $is_file_based = false; + foreach ( $terms as $term ) { + if ( '_wp_file_based' === $term->slug ) { + $is_file_based = true; + } else { + $themes[] = esc_html( wp_get_theme( $term->slug ) ); + } + } + echo implode( '
', $themes ); + if ( $is_file_based ) { + echo '
' . __( '(Created from a template file)', 'gutenberg' ); + } + return; + } +} + +/** + * Adds the auto-draft view to the templates and template parts admin lists. + * + * @param array $views The edit views to filter. + */ +function gutenberg_filter_templates_edit_views( $views ) { + $post_type = get_current_screen()->post_type; + $url = add_query_arg( + array( + 'post_type' => $post_type, + 'post_status' => 'auto-draft', + ), + 'edit.php' + ); + $is_auto_draft_view = isset( $_REQUEST['post_status'] ) && 'auto-draft' === $_REQUEST['post_status']; + $class_html = $is_auto_draft_view ? ' class="current"' : ''; + $aria_current = $is_auto_draft_view ? ' aria-current="page"' : ''; + $post_count = wp_count_posts( $post_type, 'readable' ); + $label = sprintf( + // The auto-draft status doesn't have localized labels. + translate_nooped_plural( + /* translators: %s: Number of auto-draft posts. */ + _nx_noop( + 'Auto-Draft (%s)', + 'Auto-Drafts (%s)', + 'Post status', + 'gutenberg' + ), + $post_count->{'auto-draft'} + ), + number_format_i18n( $post_count->{'auto-draft'} ) + ); + + $auto_draft_view = sprintf( + '%s', + esc_url( $url ), + $class_html, + $aria_current, + $label + ); + + array_splice( $views, 1, 0, array( 'auto-draft' => $auto_draft_view ) ); + + return $views; +} diff --git a/lib/global-styles.php b/lib/global-styles.php index 2b4173f58d9e27..6c62d867fc967d 100644 --- a/lib/global-styles.php +++ b/lib/global-styles.php @@ -14,698 +14,132 @@ function gutenberg_experimental_global_styles_has_theme_json_support() { return is_readable( locate_template( 'experimental-theme.json' ) ); } -/** - * Given a tree, it creates a flattened one - * by merging the keys and binding the leaf values - * to the new keys. - * - * It also transforms camelCase names into kebab-case - * and substitutes '/' by '-'. - * - * This is thought to be useful to generate - * CSS Custom Properties from a tree, - * although there's nothing in the implementation - * of this function that requires that format. - * - * For example, assuming the given prefix is '--wp' - * and the token is '--', for this input tree: - * - * { - * 'some/property': 'value', - * 'nestedProperty': { - * 'sub-property': 'value' - * } - * } - * - * it'll return this output: - * - * { - * '--wp--some-property': 'value', - * '--wp--nested-property--sub-property': 'value' - * } - * - * @param array $tree Input tree to process. - * @param string $prefix Prefix to prepend to each variable. '' by default. - * @param string $token Token to use between levels. '--' by default. - * - * @return array The flattened tree. - */ -function gutenberg_experimental_global_styles_get_css_vars( $tree, $prefix = '', $token = '--' ) { - $result = array(); - foreach ( $tree as $property => $value ) { - $new_key = $prefix . str_replace( - '/', - '-', - strtolower( preg_replace( '/(?get_stylesheet() ); - $recent_posts = wp_get_recent_posts( - array( - 'numberposts' => 1, - 'orderby' => 'date', - 'order' => 'desc', - 'post_type' => $post_type_filter, - 'post_status' => $post_status_filter, - 'name' => $post_name_filter, - ) - ); - - if ( is_array( $recent_posts ) && ( count( $recent_posts ) === 1 ) ) { - $user_cpt = $recent_posts[0]; - } elseif ( $should_create_cpt ) { - $cpt_post_id = wp_insert_post( - array( - 'post_content' => '{}', - 'post_status' => 'publish', - 'post_type' => $post_type_filter, - 'post_name' => $post_name_filter, - ), - true - ); - $user_cpt = get_post( $cpt_post_id, ARRAY_A ); - } - - return $user_cpt; -} - -/** - * Returns the post ID of the CPT containing the user's origin config. - * - * @return integer - */ -function gutenberg_experimental_global_styles_get_user_cpt_id() { - $user_cpt_id = null; - $user_cpt = gutenberg_experimental_global_styles_get_user_cpt( true ); - if ( array_key_exists( 'ID', $user_cpt ) ) { - $user_cpt_id = $user_cpt['ID']; - } - return $user_cpt_id; -} - -/** - * Return core's origin config. - * - * @return array Config that adheres to the theme.json schema. - */ -function gutenberg_experimental_global_styles_get_core() { - $config = gutenberg_experimental_global_styles_get_from_file( - __DIR__ . '/experimental-default-theme.json' - ); - // Start i18n logic to remove when JSON i18 strings are extracted. - $default_colors_i18n = array( - 'black' => __( 'Black', 'gutenberg' ), - 'cyan-bluish-gray' => __( 'Cyan bluish gray', 'gutenberg' ), - 'white' => __( 'White', 'gutenberg' ), - 'pale-pink' => __( 'Pale pink', 'gutenberg' ), - 'vivid-red' => __( 'Vivid red', 'gutenberg' ), - 'luminous-vivid-orange' => __( 'Luminous vivid orange', 'gutenberg' ), - 'luminous-vivid-amber' => __( 'Luminous vivid amber', 'gutenberg' ), - 'light-green-cyan' => __( 'Light green cyan', 'gutenberg' ), - 'vivid-green-cyan' => __( 'Vivid green cyan', 'gutenberg' ), - 'pale-cyan-blue' => __( 'Pale cyan blue', 'gutenberg' ), - 'vivid-cyan-blue' => __( 'Vivid cyan blue', 'gutenberg' ), - 'vivid-purple' => __( 'Vivid purple', 'gutenberg' ), - ); - - if ( ! empty( $config['global']['settings']['color']['palette'] ) ) { - foreach ( $config['global']['settings']['color']['palette'] as &$color ) { - $color['name'] = $default_colors_i18n[ $color['slug'] ]; - } - } - - $default_gradients_i18n = array( - 'vivid-cyan-blue-to-vivid-purple' => __( 'Vivid cyan blue to vivid purple', 'gutenberg' ), - 'light-green-cyan-to-vivid-green-cyan' => __( 'Light green cyan to vivid green cyan', 'gutenberg' ), - 'luminous-vivid-amber-to-luminous-vivid-orange' => __( 'Luminous vivid amber to luminous vivid orange', 'gutenberg' ), - 'luminous-vivid-orange-to-vivid-red' => __( 'Luminous vivid orange to vivid red', 'gutenberg' ), - 'very-light-gray-to-cyan-bluish-gray' => __( 'Very light gray to cyan bluish gray', 'gutenberg' ), - 'cool-to-warm-spectrum' => __( 'Cool to warm spectrum', 'gutenberg' ), - 'blush-light-purple' => __( 'Blush light purple', 'gutenberg' ), - 'blush-bordeaux' => __( 'Blush bordeaux', 'gutenberg' ), - 'luminous-dusk' => __( 'Luminous dusk', 'gutenberg' ), - 'pale-ocean' => __( 'Pale ocean', 'gutenberg' ), - 'electric-grass' => __( 'Electric grass', 'gutenberg' ), - 'midnight' => __( 'Midnight', 'gutenberg' ), - ); - - if ( ! empty( $config['global']['settings']['color']['gradients'] ) ) { - foreach ( $config['global']['settings']['color']['gradients'] as &$gradient ) { - $gradient['name'] = $default_gradients_i18n[ $gradient['slug'] ]; - } - } - - $default_font_sizes_i18n = array( - 'small' => __( 'Small', 'gutenberg' ), - 'normal' => __( 'Normal', 'gutenberg' ), - 'medium' => __( 'Medium', 'gutenberg' ), - 'large' => __( 'Large', 'gutenberg' ), - 'huge' => __( 'Huge', 'gutenberg' ), - ); - - if ( ! empty( $config['global']['settings']['typography']['fontSizes'] ) ) { - foreach ( $config['global']['settings']['typography']['fontSizes'] as &$font_size ) { - $font_size['name'] = $default_font_sizes_i18n[ $font_size['slug'] ]; - } - } - // End i18n logic to remove when JSON i18 strings are extracted. - return $config; -} - /** * Returns the theme presets registered via add_theme_support, if any. * + * @param array $settings Existing editor settings. + * * @return array Config that adheres to the theme.json schema. */ -function gutenberg_experimental_global_styles_get_theme_support_settings() { +function gutenberg_experimental_global_styles_get_theme_support_settings( $settings ) { $theme_settings = array(); $theme_settings['global'] = array(); $theme_settings['global']['settings'] = array(); // Deprecated theme supports. - if ( get_theme_support( 'disable-custom-colors' ) ) { + if ( isset( $settings['disableCustomColors'] ) ) { if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { $theme_settings['global']['settings']['color'] = array(); } - $theme_settings['global']['settings']['color']['custom'] = false; + $theme_settings['global']['settings']['color']['custom'] = ! $settings['disableCustomColors']; } - if ( get_theme_support( 'disable-custom-gradients' ) ) { + + if ( isset( $settings['disableCustomGradients'] ) ) { if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { $theme_settings['global']['settings']['color'] = array(); } - $theme_settings['global']['settings']['color']['customGradient'] = false; + $theme_settings['global']['settings']['color']['customGradient'] = ! $settings['disableCustomGradients']; } - if ( get_theme_support( 'disable-custom-font-sizes' ) ) { + + if ( isset( $settings['disableCustomFontSizes'] ) ) { if ( ! isset( $theme_settings['global']['settings']['typography'] ) ) { $theme_settings['global']['settings']['typography'] = array(); } - $theme_settings['global']['settings']['typography']['customFontSize'] = false; + $theme_settings['global']['settings']['typography']['customFontSize'] = ! $settings['disableCustomFontSizes']; } - if ( get_theme_support( 'custom-line-height' ) ) { + + if ( isset( $settings['enableCustomLineHeight'] ) ) { if ( ! isset( $theme_settings['global']['settings']['typography'] ) ) { $theme_settings['global']['settings']['typography'] = array(); } - $theme_settings['global']['settings']['typography']['customLineHeight'] = true; - } - if ( get_theme_support( 'custom-spacing' ) ) { - if ( ! isset( $theme_settings['global']['settings']['spacing'] ) ) { - $theme_settings['global']['settings']['spacing'] = array(); - } - $theme_settings['global']['settings']['spacing']['custom'] = true; - } - if ( get_theme_support( 'experimental-link-color' ) ) { - if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { - $theme_settings['global']['settings']['color'] = array(); - } - $theme_settings['global']['settings']['color']['link'] = true; + $theme_settings['global']['settings']['typography']['customLineHeight'] = $settings['enableCustomLineHeight']; } - $custom_units_theme_support = get_theme_support( 'custom-units' ); - if ( $custom_units_theme_support ) { + if ( isset( $settings['enableCustomUnits'] ) ) { if ( ! isset( $theme_settings['global']['settings']['spacing'] ) ) { $theme_settings['global']['settings']['spacing'] = array(); } - $theme_settings['global']['settings']['spacing'] ['units'] = true === $custom_units_theme_support ? array( 'px', 'em', 'rem', 'vh', 'vw' ) : $custom_units_theme_support; + $theme_settings['global']['settings']['spacing']['units'] = ( true === $settings['enableCustomUnits'] ) ? + array( 'px', 'em', 'rem', 'vh', 'vw' ) : + $settings['enableCustomUnits']; } - $theme_colors = get_theme_support( 'editor-color-palette' ); - if ( ! empty( $theme_colors[0] ) ) { + if ( isset( $settings['colors'] ) ) { if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { $theme_settings['global']['settings']['color'] = array(); } - $theme_settings['global']['settings']['color']['palette'] = array(); - $theme_settings['global']['settings']['color']['palette'] = $theme_colors[0]; + $theme_settings['global']['settings']['color']['palette'] = $settings['colors']; } - $theme_gradients = get_theme_support( 'editor-gradient-presets' ); - if ( ! empty( $theme_gradients[0] ) ) { + if ( isset( $settings['gradients'] ) ) { if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { $theme_settings['global']['settings']['color'] = array(); } - $theme_settings['global']['settings']['color']['gradients'] = array(); - $theme_settings['global']['settings']['color']['gradients'] = $theme_gradients[0]; + $theme_settings['global']['settings']['color']['gradients'] = $settings['gradients']; } - $theme_font_sizes = get_theme_support( 'editor-font-sizes' ); - if ( ! empty( $theme_font_sizes[0] ) ) { + if ( isset( $settings['fontSizes'] ) ) { + $font_sizes = $settings['fontSizes']; + // Back-compatibility for presets without units. + foreach ( $font_sizes as &$font_size ) { + if ( is_numeric( $font_size['size'] ) ) { + $font_size['size'] = $font_size['size'] . 'px'; + } + } if ( ! isset( $theme_settings['global']['settings']['typography'] ) ) { $theme_settings['global']['settings']['typography'] = array(); } - $theme_settings['global']['settings']['typography']['fontSizes'] = array(); - $theme_settings['global']['settings']['typography']['fontSizes'] = $theme_font_sizes[0]; - } - - return $theme_settings; -} - -/** - * Returns the theme's origin config. - * - * It also fetches the existing presets the theme declared via add_theme_support - * and uses them if the theme hasn't declared any via theme.json. - * - * @return array Config that adheres to the theme.json schema. - */ -function gutenberg_experimental_global_styles_get_theme() { - $theme_support_settings = gutenberg_experimental_global_styles_get_theme_support_settings(); - $theme_config = gutenberg_experimental_global_styles_get_from_file( - locate_template( 'experimental-theme.json' ) - ); - - /* - * We want the presets declared in theme.json - * to take precedence over the ones declared via add_theme_support. - * - * Note that merging happens at the preset category level. Example: - * - * - if the theme declares a color palette via add_theme_support & - * a set of font sizes via theme.json, both will be included in the output. - * - * - if the theme declares a color palette both via add_theme_support & - * via theme.json, the later takes precedence. - * - */ - $theme_config = gutenberg_experimental_global_styles_merge_trees( - $theme_support_settings, - $theme_config - ); - - return $theme_config; -} - -/** - * Convert style property to its CSS name. - * - * @param string $style_property Style property name. - * @return string CSS property name. - */ -function gutenberg_experimental_global_styles_get_css_property( $style_property ) { - switch ( $style_property ) { - case 'backgroundColor': - return 'background-color'; - case 'fontSize': - return 'font-size'; - case 'lineHeight': - return 'line-height'; - default: - return $style_property; + $theme_settings['global']['settings']['typography']['fontSizes'] = $font_sizes; } -} - -/** - * Return how the style property is structured. - * - * @return array Style property structure. - */ -function gutenberg_experimental_global_styles_get_style_property() { - return array( - '--wp--style--color--link' => array( 'color', 'link' ), - 'background' => array( 'color', 'gradient' ), - 'backgroundColor' => array( 'color', 'background' ), - 'color' => array( 'color', 'text' ), - 'fontSize' => array( 'typography', 'fontSize' ), - 'lineHeight' => array( 'typography', 'lineHeight' ), - ); -} - -/** - * Return how the support keys are structured. - * - * @return array Support keys structure. - */ -function gutenberg_experimental_global_styles_get_support_keys() { - return array( - '--wp--style--color--link' => array( 'color', 'linkColor' ), - 'background' => array( 'color', 'gradients' ), - 'backgroundColor' => array( 'color' ), - 'color' => array( 'color' ), - 'fontSize' => array( 'fontSize' ), - 'lineHeight' => array( 'lineHeight' ), - ); -} -/** - * Returns how the presets css variables are structured on the global styles data. - * - * @return array Presets structure - */ -function gutenberg_experimental_global_styles_get_presets_structure() { - return array( - 'color' => array( - 'path' => array( 'color', 'palette' ), - 'key' => 'color', - ), - 'gradient' => array( - 'path' => array( 'color', 'gradients' ), - 'key' => 'gradient', - ), - 'fontSize' => array( - 'path' => array( 'typography', 'fontSizes' ), - 'key' => 'size', - ), - ); -} - -/** - * Returns the style features a particular block supports. - * - * @param array $supports The block supports array. - * - * @return array Style features supported by the block. - */ -function gutenberg_experimental_global_styles_get_supported_styles( $supports ) { - $support_keys = gutenberg_experimental_global_styles_get_support_keys(); - $supported_features = array(); - foreach ( $support_keys as $key => $path ) { - if ( gutenberg_experimental_get( $supports, $path ) ) { - $supported_features[] = $key; - } - } - - return $supported_features; -} - -/** - * Retrieves the block data (selector/supports). - * - * @return array - */ -function gutenberg_experimental_global_styles_get_block_data() { - $block_data = array(); - - $registry = WP_Block_Type_Registry::get_instance(); - $blocks = array_merge( - $registry->get_all_registered(), - array( - 'global' => new WP_Block_Type( - 'global', - array( - 'supports' => array( - '__experimentalSelector' => ':root', - 'fontSize' => true, - 'color' => array( - 'linkColor' => true, - 'gradients' => true, - ), - ), - ) - ), - ) - ); - foreach ( $blocks as $block_name => $block_type ) { - if ( ! property_exists( $block_type, 'supports' ) || empty( $block_type->supports ) || ! is_array( $block_type->supports ) ) { - continue; - } - - $supports = gutenberg_experimental_global_styles_get_supported_styles( $block_type->supports ); - - /* - * Assign the selector for the block. - * - * Some blocks can declare multiple selectors: - * - * - core/heading represents the H1-H6 HTML elements - * - core/list represents the UL and OL HTML elements - * - core/group is meant to represent DIV and other HTML elements - * - * Some other blocks don't provide a selector, - * so we generate a class for them based on their name: - * - * - 'core/group' => '.wp-block-group' - * - 'my-custom-library/block-name' => '.wp-block-my-custom-library-block-name' - * - * Note that, for core blocks, we don't add the `core/` prefix to its class name. - * This is for historical reasons, as they come with a class without that infix. - * - */ - if ( - isset( $block_type->supports['__experimentalSelector'] ) && - is_string( $block_type->supports['__experimentalSelector'] ) - ) { - $block_data[ $block_name ] = array( - 'selector' => $block_type->supports['__experimentalSelector'], - 'supports' => $supports, - 'blockName' => $block_name, - ); - } elseif ( - isset( $block_type->supports['__experimentalSelector'] ) && - is_array( $block_type->supports['__experimentalSelector'] ) - ) { - foreach ( $block_type->supports['__experimentalSelector'] as $key => $selector ) { - $block_data[ $key ] = array( - 'selector' => $selector, - 'supports' => $supports, - 'blockName' => $block_name, - ); - } - } else { - $block_data[ $block_name ] = array( - 'selector' => '.wp-block-' . str_replace( '/', '-', str_replace( 'core/', '', $block_name ) ), - 'supports' => $supports, - 'blockName' => $block_name, - ); + // Things that didn't land in core yet, so didn't have a setting assigned. + if ( current( (array) get_theme_support( 'custom-spacing' ) ) ) { + if ( ! isset( $theme_settings['global']['settings']['spacing'] ) ) { + $theme_settings['global']['settings']['spacing'] = array(); } + $theme_settings['global']['settings']['spacing']['customPadding'] = true; } - return $block_data; -} - -/** - * Given an array contain the styles shape returns the css for this styles. - * A similar function exists on the client at /packages/block-editor/src/hooks/style.js. - * - * @param array $styles Array containing the styles shape from global styles. - * - * @return array Containing a set of css rules. - */ -function gutenberg_experimental_global_styles_flatten_styles_tree( $styles ) { - $mappings = gutenberg_experimental_global_styles_get_style_property(); - - $result = array(); - foreach ( $mappings as $key => $path ) { - $value = gutenberg_experimental_get( $styles, $path, null ); - if ( null !== $value ) { - $result[ $key ] = $value; + if ( current( (array) get_theme_support( 'experimental-link-color' ) ) ) { + if ( ! isset( $theme_settings['global']['settings']['color'] ) ) { + $theme_settings['global']['settings']['color'] = array(); } + $theme_settings['global']['settings']['color']['link'] = true; } - return $result; - -} - -/** - * Given a selector for a block, and the settings of the block, returns a string - * with the stylesheet of the preset classes required for that block. - * - * @param string $selector String with the CSS selector for the block. - * @param array $settings Array containing the settings of the block. - * - * @return string Stylesheet with the preset classes. - */ -function gutenberg_experimental_global_styles_get_preset_classes( $selector, $settings ) { - if ( empty( $settings ) || empty( $selector ) ) { - return ''; - } - - $stylesheet = ''; - $class_prefix = 'has'; - $classes_structure = array( - 'color' => array( - 'path' => array( 'color', 'palette' ), - 'key' => 'color', - 'property' => 'color', - ), - 'background-color' => array( - 'path' => array( 'color', 'palette' ), - 'key' => 'color', - 'property' => 'background-color', - ), - 'gradient-background' => array( - 'path' => array( 'color', 'gradients' ), - 'key' => 'gradient', - 'property' => 'background', - ), - 'font-size' => array( - 'path' => array( 'typography', 'fontSizes' ), - 'key' => 'size', - 'property' => 'font-size', - ), - ); - foreach ( $classes_structure as $class_suffix => $preset_structure ) { - $path = $preset_structure['path']; - $presets = gutenberg_experimental_get( $settings, $path ); - - if ( empty( $presets ) ) { - continue; - } - - $key = $preset_structure['key']; - $property = $preset_structure['property']; - - foreach ( $presets as $preset ) { - $slug = $preset['slug']; - $value = $preset[ $key ]; - - $class_to_use = ".$class_prefix-$slug-$class_suffix"; - $selector_to_use = ''; - if ( ':root' === $selector ) { - $selector_to_use = $class_to_use; - } else { - $selector_to_use = "$selector$class_to_use"; - } - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { - $stylesheet .= "$selector_to_use {\n\t$property: $value;\n}\n"; - } else { - $stylesheet .= $selector_to_use . '{' . "$property:$value;}\n"; - } - } - } - return $stylesheet; + return $theme_settings; } /** * Takes a tree adhering to the theme.json schema and generates * the corresponding stylesheet. * - * @param array $tree Input tree. + * @param WP_Theme_JSON $tree Input tree. + * @param string $type Type of stylesheet we want accepts 'all', 'block_styles', and 'css_variables'. * * @return string Stylesheet. */ -function gutenberg_experimental_global_styles_get_stylesheet( $tree ) { - $stylesheet = ''; - $block_data = gutenberg_experimental_global_styles_get_block_data(); - foreach ( array_keys( $tree ) as $block_name ) { - if ( - ! array_key_exists( $block_name, $block_data ) || - ! array_key_exists( 'selector', $block_data[ $block_name ] ) || - ! array_key_exists( 'supports', $block_data[ $block_name ] ) - ) { - // Skip blocks that haven't declared support, - // because we don't know to process them. - continue; - } +function gutenberg_experimental_global_styles_get_stylesheet( $tree, $type = 'all' ) { + // Check if we can use cached. + $can_use_cached = ( + ( 'all' === $type ) && + ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && + ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) && + ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && + ! is_admin() + ); - // Create the CSS Custom Properties for the presets. - $computed_presets = array(); - $presets_structure = gutenberg_experimental_global_styles_get_presets_structure(); - foreach ( $presets_structure as $token => $preset_meta ) { - $block_preset = gutenberg_experimental_get( $tree[ $block_name ]['settings'], $preset_meta['path'] ); - if ( ! empty( $block_preset ) ) { - $computed_presets[ $token ] = array(); - foreach ( $block_preset as $preset_value ) { - $computed_presets[ $token ][ $preset_value['slug'] ] = $preset_value[ $preset_meta['key'] ]; - } - } + if ( $can_use_cached ) { + // Check if we have the styles already cached. + $cached = get_transient( 'global_styles' ); + if ( $cached ) { + return $cached; } - $token = '--'; - $preset_prefix = '--wp--preset' . $token; - $preset_variables = gutenberg_experimental_global_styles_get_css_vars( $computed_presets, $preset_prefix, $token ); - - // Create the CSS Custom Properties that are specific to the theme. - $computed_theme_props = gutenberg_experimental_get( $tree[ $block_name ]['settings'], array( 'custom' ) ); - $theme_props_prefix = '--wp--custom' . $token; - $theme_variables = gutenberg_experimental_global_styles_get_css_vars( - $computed_theme_props, - $theme_props_prefix, - $token - ); - - $stylesheet .= gutenberg_experimental_global_styles_resolver_styles( - $block_data[ $block_name ]['selector'], - $block_data[ $block_name ]['supports'], - array_merge( - gutenberg_experimental_global_styles_flatten_styles_tree( $tree[ $block_name ]['styles'] ), - $preset_variables, - $theme_variables - ) - ); - - $stylesheet .= gutenberg_experimental_global_styles_get_preset_classes( $block_data[ $block_name ]['selector'], $tree[ $block_name ]['settings'] ); } - if ( gutenberg_experimental_global_styles_has_theme_json_support() ) { + $stylesheet = $tree->get_stylesheet( $type ); + + if ( ( 'all' === $type || 'block_styles' === $type ) && gutenberg_experimental_global_styles_has_theme_json_support() ) { // To support all themes, we added in the block-library stylesheet // a style rule such as .has-link-color a { color: var(--wp--style--color--link, #00e); } // so that existing link colors themes used didn't break. @@ -714,140 +148,13 @@ function gutenberg_experimental_global_styles_get_stylesheet( $tree ) { $stylesheet .= 'a{color:var(--wp--style--color--link, #00e);}'; } - return $stylesheet; -} - -/** - * Generates CSS declarations for a block. - * - * @param string $block_selector CSS selector for the block. - * @param array $block_supports A list of properties supported by the block. - * @param array $block_styles The list of properties/values to be converted to CSS. - * - * @return string The corresponding CSS rule. - */ -function gutenberg_experimental_global_styles_resolver_styles( $block_selector, $block_supports, $block_styles ) { - $css_property = ''; - $css_rule = ''; - $css_declarations = ''; - - foreach ( $block_styles as $property => $value ) { - // Only convert to CSS: - // - // 1) The style attributes the block has declared support for. - // 2) Any CSS custom property attached to the node. - if ( - in_array( $property, $block_supports, true ) || - strstr( $property, '--' ) - ) { - $css_property = gutenberg_experimental_global_styles_get_css_property( $property ); - - // Add whitespace if SCRIPT_DEBUG is defined and set to true. - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { - $css_declarations .= "\t" . $css_property . ': ' . $value . ";\n"; - } else { - $css_declarations .= $css_property . ':' . $value . ';'; - } - } + if ( $can_use_cached ) { + // Cache for a minute. + // This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites. + set_transient( 'global_styles', $stylesheet, MINUTE_IN_SECONDS ); } - if ( '' !== $css_declarations ) { - - // Add whitespace if SCRIPT_DEBUG is defined and set to true. - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { - $css_rule .= $block_selector . " {\n"; - $css_rule .= $css_declarations; - $css_rule .= "}\n"; - } else { - $css_rule .= $block_selector . '{' . $css_declarations . '}'; - } - } - - return $css_rule; -} - -/** - * Helper function that merges trees that adhere to the theme.json schema. - * - * @param array $core Core origin. - * @param array $theme Theme origin. - * @param array $user User origin. An empty array by default. - * - * @return array The merged result. - */ -function gutenberg_experimental_global_styles_merge_trees( $core, $theme, $user = array() ) { - $core = gutenberg_experimental_global_styles_normalize_schema( $core ); - $theme = gutenberg_experimental_global_styles_normalize_schema( $theme ); - $user = gutenberg_experimental_global_styles_normalize_schema( $user ); - $result = gutenberg_experimental_global_styles_normalize_schema( array() ); - - foreach ( array_keys( $core ) as $block_name ) { - foreach ( array_keys( $core[ $block_name ]['settings'] ) as $subtree ) { - $result[ $block_name ]['settings'][ $subtree ] = array_merge( - $core[ $block_name ]['settings'][ $subtree ], - $theme[ $block_name ]['settings'][ $subtree ], - $user[ $block_name ]['settings'][ $subtree ] - ); - } - foreach ( array_keys( $core[ $block_name ]['styles'] ) as $subtree ) { - $result[ $block_name ]['styles'][ $subtree ] = array_merge( - $core[ $block_name ]['styles'][ $subtree ], - $theme[ $block_name ]['styles'][ $subtree ], - $user[ $block_name ]['styles'][ $subtree ] - ); - } - } - - return $result; -} - -/** - * Given a tree, it normalizes it to the expected schema. - * - * @param array $tree Source tree to normalize. - * - * @return array Normalized tree. - */ -function gutenberg_experimental_global_styles_normalize_schema( $tree ) { - $block_schema = array( - 'styles' => array( - 'typography' => array(), - 'color' => array(), - ), - 'settings' => array( - 'color' => array(), - 'custom' => array(), - 'typography' => array(), - 'spacing' => array(), - ), - ); - - $normalized_tree = array(); - $block_data = gutenberg_experimental_global_styles_get_block_data(); - foreach ( array_keys( $block_data ) as $block_name ) { - $normalized_tree[ $block_name ] = $block_schema; - } - - $tree = array_merge_recursive( - $normalized_tree, - $tree - ); - - return $tree; -} - -/** - * Takes data from the different origins (core, theme, and user) - * and returns the merged result. - * - * @return array Merged trees - */ -function gutenberg_experimental_global_styles_get_merged_origins() { - $core = gutenberg_experimental_global_styles_get_core(); - $theme = gutenberg_experimental_global_styles_get_theme(); - $user = gutenberg_experimental_global_styles_get_user(); - - return gutenberg_experimental_global_styles_merge_trees( $core, $theme, $user ); + return $stylesheet; } /** @@ -855,8 +162,13 @@ function gutenberg_experimental_global_styles_get_merged_origins() { * and enqueues the resulting stylesheet. */ function gutenberg_experimental_global_styles_enqueue_assets() { - $merged = gutenberg_experimental_global_styles_get_merged_origins(); - $stylesheet = gutenberg_experimental_global_styles_get_stylesheet( $merged ); + $settings = gutenberg_get_common_block_editor_settings(); + $theme_support_data = gutenberg_experimental_global_styles_get_theme_support_settings( $settings ); + + $resolver = new WP_Theme_JSON_Resolver(); + $all = $resolver->get_origin( $theme_support_data ); + + $stylesheet = gutenberg_experimental_global_styles_get_stylesheet( $all ); if ( empty( $stylesheet ) ) { return; } @@ -866,28 +178,6 @@ function gutenberg_experimental_global_styles_enqueue_assets() { wp_enqueue_style( 'global-styles' ); } -/** - * Returns the default config for editor features, - * or an empty array if none found. - * - * @param array $config Config to extract values from. - * @return array Default features config for the editor. - */ -function gutenberg_experimental_global_styles_get_editor_settings( $config ) { - $settings = array(); - foreach ( array_keys( $config ) as $context ) { - if ( - empty( $config[ $context ]['settings'] ) || - ! is_array( $config[ $context ]['settings'] ) - ) { - $settings[ $context ] = array(); - } else { - $settings[ $context ] = $config[ $context ]['settings']; - } - } - return $settings; -} - /** * Adds the necessary data for the Global Styles client UI to the block settings. * @@ -895,21 +185,23 @@ function gutenberg_experimental_global_styles_get_editor_settings( $config ) { * @return array New block editor settings */ function gutenberg_experimental_global_styles_settings( $settings ) { - $merged = gutenberg_experimental_global_styles_get_merged_origins(); - - // STEP 1: ADD FEATURES - // These need to be added to settings always. - // We also need to unset the deprecated settings defined by core. - $settings['__experimentalFeatures'] = gutenberg_experimental_global_styles_get_editor_settings( $merged ); - + $theme_support_data = gutenberg_experimental_global_styles_get_theme_support_settings( $settings ); unset( $settings['colors'] ); - unset( $settings['gradients'] ); - unset( $settings['fontSizes'] ); unset( $settings['disableCustomColors'] ); - unset( $settings['disableCustomGradients'] ); unset( $settings['disableCustomFontSizes'] ); + unset( $settings['disableCustomGradients'] ); unset( $settings['enableCustomLineHeight'] ); unset( $settings['enableCustomUnits'] ); + unset( $settings['fontSizes'] ); + unset( $settings['gradients'] ); + + $resolver = new WP_Theme_JSON_Resolver(); + $all = $resolver->get_origin( $theme_support_data ); + $base = $resolver->get_origin( $theme_support_data, 'theme' ); + + // STEP 1: ADD FEATURES + // These need to be added to settings always. + $settings['__experimentalFeatures'] = $all->get_settings(); // STEP 2 - IF EDIT-SITE, ADD DATA REQUIRED FOR GLOBAL STYLES SIDEBAR // The client needs some information to be able to access/update the user styles. @@ -922,12 +214,8 @@ function_exists( 'gutenberg_is_edit_site_page' ) && gutenberg_is_edit_site_page( $screen->id ) && gutenberg_experimental_global_styles_has_theme_json_support() ) { - $settings['__experimentalGlobalStylesUserEntityId'] = gutenberg_experimental_global_styles_get_user_cpt_id(); - $settings['__experimentalGlobalStylesContexts'] = gutenberg_experimental_global_styles_get_block_data(); - $settings['__experimentalGlobalStylesBaseStyles'] = gutenberg_experimental_global_styles_merge_trees( - gutenberg_experimental_global_styles_get_core(), - gutenberg_experimental_global_styles_get_theme() - ); + $settings['__experimentalGlobalStylesUserEntityId'] = WP_Theme_JSON_Resolver::get_user_custom_post_type_id(); + $settings['__experimentalGlobalStylesBaseStyles'] = $base->get_raw_data(); } else { // STEP 3 - OTHERWISE, ADD STYLES // @@ -935,45 +223,18 @@ function_exists( 'gutenberg_is_edit_site_page' ) && // we need to add the styles via the settings. This is because // we want them processed as if they were added via add_editor_styles, // which adds the editor wrapper class. - $settings['styles'][] = array( 'css' => gutenberg_experimental_global_styles_get_stylesheet( $merged ) ); + $settings['styles'][] = array( + 'css' => gutenberg_experimental_global_styles_get_stylesheet( $all, 'css_variables' ), + '__experimentalNoWrapper' => true, + ); + $settings['styles'][] = array( + 'css' => gutenberg_experimental_global_styles_get_stylesheet( $all, 'block_styles' ), + ); } return $settings; } -/** - * Registers a Custom Post Type to store the user's origin config. - */ -function gutenberg_experimental_global_styles_register_cpt() { - if ( ! gutenberg_experimental_global_styles_has_theme_json_support() ) { - return; - } - - $args = array( - 'label' => __( 'Global Styles', 'gutenberg' ), - 'description' => 'CPT to store user design tokens', - 'public' => false, - 'show_ui' => false, - 'show_in_rest' => true, - 'rest_base' => '__experimental/global-styles', - 'capabilities' => array( - 'read' => 'edit_theme_options', - 'create_posts' => 'edit_theme_options', - 'edit_posts' => 'edit_theme_options', - 'edit_published_posts' => 'edit_theme_options', - 'delete_published_posts' => 'edit_theme_options', - 'edit_others_posts' => 'edit_theme_options', - 'delete_others_posts' => 'edit_theme_options', - ), - 'map_meta_cap' => true, - 'supports' => array( - 'editor', - 'revisions', - ), - ); - register_post_type( 'wp_global_styles', $args ); -} - -add_action( 'init', 'gutenberg_experimental_global_styles_register_cpt' ); -add_filter( 'block_editor_settings', 'gutenberg_experimental_global_styles_settings' ); +add_action( 'init', array( 'WP_Theme_JSON_Resolver', 'register_user_custom_post_type' ) ); +add_filter( 'block_editor_settings', 'gutenberg_experimental_global_styles_settings', PHP_INT_MAX ); add_action( 'wp_enqueue_scripts', 'gutenberg_experimental_global_styles_enqueue_assets' ); diff --git a/lib/load.php b/lib/load.php index 42ebfe7bb5733d..82686d85ca45f8 100644 --- a/lib/load.php +++ b/lib/load.php @@ -30,102 +30,109 @@ function gutenberg_is_experiment_enabled( $name ) { * Start: Include for phase 2 */ if ( ! class_exists( 'WP_REST_Sidebars_Controller' ) ) { - require_once dirname( __FILE__ ) . '/class-wp-rest-sidebars-controller.php'; + require_once __DIR__ . '/class-wp-rest-sidebars-controller.php'; } if ( ! class_exists( 'WP_REST_Widget_Types_Controller' ) ) { - require_once dirname( __FILE__ ) . '/class-wp-rest-widget-types-controller.php'; + require_once __DIR__ . '/class-wp-rest-widget-types-controller.php'; } if ( ! class_exists( 'WP_REST_Widgets_Controller' ) ) { - require_once dirname( __FILE__ ) . '/class-wp-rest-widgets-controller.php'; + require_once __DIR__ . '/class-wp-rest-widgets-controller.php'; } if ( ! class_exists( 'WP_REST_Block_Directory_Controller' ) ) { - require dirname( __FILE__ ) . '/class-wp-rest-block-directory-controller.php'; + require __DIR__ . '/class-wp-rest-block-directory-controller.php'; } if ( ! class_exists( 'WP_REST_Block_Types_Controller' ) ) { - require dirname( __FILE__ ) . '/class-wp-rest-block-types-controller.php'; + require __DIR__ . '/class-wp-rest-block-types-controller.php'; } if ( ! class_exists( 'WP_REST_Menus_Controller' ) ) { - require_once dirname( __FILE__ ) . '/class-wp-rest-menus-controller.php'; + require_once __DIR__ . '/class-wp-rest-menus-controller.php'; } if ( ! class_exists( 'WP_REST_Menu_Items_Controller' ) ) { - require_once dirname( __FILE__ ) . '/class-wp-rest-menu-items-controller.php'; + require_once __DIR__ . '/class-wp-rest-menu-items-controller.php'; } if ( ! class_exists( 'WP_REST_Menu_Locations_Controller' ) ) { - require_once dirname( __FILE__ ) . '/class-wp-rest-menu-locations-controller.php'; + require_once __DIR__ . '/class-wp-rest-menu-locations-controller.php'; } if ( ! class_exists( 'WP_Rest_Customizer_Nonces' ) ) { - require_once dirname( __FILE__ ) . '/class-wp-rest-customizer-nonces.php'; + require_once __DIR__ . '/class-wp-rest-customizer-nonces.php'; } if ( ! class_exists( 'WP_REST_Image_Editor_Controller' ) ) { - require dirname( __FILE__ ) . '/class-wp-rest-image-editor-controller.php'; + require __DIR__ . '/class-wp-rest-image-editor-controller.php'; } if ( ! class_exists( 'WP_REST_Plugins_Controller' ) ) { - require_once dirname( __FILE__ ) . '/class-wp-rest-plugins-controller.php'; + require_once __DIR__ . '/class-wp-rest-plugins-controller.php'; } if ( ! class_exists( 'WP_REST_Post_Format_Search_Handler' ) ) { - require_once dirname( __FILE__ ) . '/class-wp-rest-post-format-search-handler.php'; + require_once __DIR__ . '/class-wp-rest-post-format-search-handler.php'; } if ( ! class_exists( 'WP_REST_Term_Search_Handler' ) ) { - require_once dirname( __FILE__ ) . '/class-wp-rest-term-search-handler.php'; + require_once __DIR__ . '/class-wp-rest-term-search-handler.php'; } if ( ! class_exists( 'WP_REST_Batch_Controller' ) ) { - require_once dirname( __FILE__ ) . '/class-wp-rest-batch-controller.php'; + require_once __DIR__ . '/class-wp-rest-batch-controller.php'; } /** * End: Include for phase 2 */ - require dirname( __FILE__ ) . '/rest-api.php'; + require __DIR__ . '/rest-api.php'; } if ( ! class_exists( 'WP_Block_Patterns_Registry' ) ) { - require dirname( __FILE__ ) . '/class-wp-block-patterns-registry.php'; + require __DIR__ . '/class-wp-block-patterns-registry.php'; } if ( ! class_exists( 'WP_Block_Pattern_Categories_Registry' ) ) { - require dirname( __FILE__ ) . '/class-wp-block-pattern-categories-registry.php'; + require __DIR__ . '/class-wp-block-pattern-categories-registry.php'; } if ( ! class_exists( 'WP_Block' ) ) { - require dirname( __FILE__ ) . '/class-wp-block.php'; + require __DIR__ . '/class-wp-block.php'; } if ( ! class_exists( 'WP_Block_List' ) ) { - require dirname( __FILE__ ) . '/class-wp-block-list.php'; + require __DIR__ . '/class-wp-block-list.php'; } + if ( ! class_exists( 'WP_Widget_Block' ) ) { - require_once dirname( __FILE__ ) . '/class-wp-widget-block.php'; + require_once __DIR__ . '/class-wp-widget-block.php'; } -require_once dirname( __FILE__ ) . '/widgets-page.php'; -require dirname( __FILE__ ) . '/compat.php'; -require dirname( __FILE__ ) . '/utils.php'; +require_once __DIR__ . '/widgets-page.php'; -// Include FSE related files only if the experiment is enabled. -if ( gutenberg_is_experiment_enabled( 'gutenberg-full-site-editing' ) ) { - require dirname( __FILE__ ) . '/templates.php'; - require dirname( __FILE__ ) . '/template-parts.php'; - require dirname( __FILE__ ) . '/template-loader.php'; - require dirname( __FILE__ ) . '/edit-site-page.php'; - require dirname( __FILE__ ) . '/edit-site-export.php'; -} +require __DIR__ . '/compat.php'; +require __DIR__ . '/utils.php'; +require __DIR__ . '/editor-settings.php'; + +require __DIR__ . '/full-site-editing.php'; +require __DIR__ . '/full-site-editing/default-template-types.php'; +require __DIR__ . '/full-site-editing/templates-utils.php'; +require __DIR__ . '/templates-sync.php'; +require __DIR__ . '/templates.php'; +require __DIR__ . '/template-parts.php'; +require __DIR__ . '/template-loader.php'; +require __DIR__ . '/edit-site-page.php'; +require __DIR__ . '/edit-site-export.php'; -require dirname( __FILE__ ) . '/block-patterns.php'; -require dirname( __FILE__ ) . '/blocks.php'; -require dirname( __FILE__ ) . '/client-assets.php'; -require dirname( __FILE__ ) . '/block-directory.php'; -require dirname( __FILE__ ) . '/demo.php'; -require dirname( __FILE__ ) . '/widgets.php'; -require dirname( __FILE__ ) . '/navigation.php'; -require dirname( __FILE__ ) . '/navigation-page.php'; -require dirname( __FILE__ ) . '/experiments-page.php'; -require dirname( __FILE__ ) . '/global-styles.php'; +require __DIR__ . '/block-patterns.php'; +require __DIR__ . '/blocks.php'; +require __DIR__ . '/client-assets.php'; +require __DIR__ . '/block-directory.php'; +require __DIR__ . '/demo.php'; +require __DIR__ . '/widgets.php'; +require __DIR__ . '/navigation.php'; +require __DIR__ . '/navigation-page.php'; +require __DIR__ . '/experiments-page.php'; +require __DIR__ . '/class-wp-theme-json.php'; +require __DIR__ . '/class-wp-theme-json-resolver.php'; +require __DIR__ . '/global-styles.php'; if ( ! class_exists( 'WP_Block_Supports' ) ) { - require_once dirname( __FILE__ ) . '/class-wp-block-supports.php'; + require_once __DIR__ . '/class-wp-block-supports.php'; } -require dirname( __FILE__ ) . '/block-supports/generated-classname.php'; -require dirname( __FILE__ ) . '/block-supports/colors.php'; -require dirname( __FILE__ ) . '/block-supports/align.php'; -require dirname( __FILE__ ) . '/block-supports/typography.php'; -require dirname( __FILE__ ) . '/block-supports/custom-classname.php'; +require __DIR__ . '/block-supports/generated-classname.php'; +require __DIR__ . '/block-supports/colors.php'; +require __DIR__ . '/block-supports/align.php'; +require __DIR__ . '/block-supports/typography.php'; +require __DIR__ . '/block-supports/custom-classname.php'; +require __DIR__ . '/block-supports/border.php'; diff --git a/lib/navigation-page.php b/lib/navigation-page.php index 16be155850b4b5..98398cc238e394 100644 --- a/lib/navigation-page.php +++ b/lib/navigation-page.php @@ -32,44 +32,12 @@ function gutenberg_navigation_init( $hook ) { return; } - // Media settings. - $max_upload_size = wp_max_upload_size(); - if ( ! $max_upload_size ) { - $max_upload_size = 0; - } - - /** This filter is documented in wp-admin/includes/media.php */ - $image_size_names = apply_filters( - 'image_size_names_choose', + $settings = array_merge( + gutenberg_get_common_block_editor_settings(), array( - 'thumbnail' => __( 'Thumbnail', 'gutenberg' ), - 'medium' => __( 'Medium', 'gutenberg' ), - 'large' => __( 'Large', 'gutenberg' ), - 'full' => __( 'Full Size', 'gutenberg' ), + 'blockNavMenus' => get_theme_support( 'block-nav-menus' ), ) ); - - $available_image_sizes = array(); - foreach ( $image_size_names as $image_size_slug => $image_size_name ) { - $available_image_sizes[] = array( - 'slug' => $image_size_slug, - 'name' => $image_size_name, - ); - } - - $settings = array( - 'imageSizes' => $available_image_sizes, - 'isRTL' => is_rtl(), - 'maxUploadFileSize' => $max_upload_size, - 'blockNavMenus' => get_theme_support( 'block-nav-menus' ), - ); - - list( $font_sizes, ) = (array) get_theme_support( 'editor-font-sizes' ); - - if ( false !== $font_sizes ) { - $settings['fontSizes'] = $font_sizes; - } - $settings = gutenberg_experimental_global_styles_settings( $settings ); wp_add_inline_script( @@ -78,7 +46,7 @@ function gutenberg_navigation_init( $hook ) { 'wp.domReady( function() { wp.editNavigation.initialize( "navigation-editor", %s ); } );', - wp_json_encode( gutenberg_experiments_editor_settings( $settings ) ) + wp_json_encode( $settings ) ) ); diff --git a/lib/patterns/heading-paragraph.php b/lib/patterns/heading-paragraph.php index 9a8b160a787727..d06caa1d8cc2c8 100644 --- a/lib/patterns/heading-paragraph.php +++ b/lib/patterns/heading-paragraph.php @@ -8,7 +8,6 @@ return array( 'title' => __( 'Heading and paragraph', 'gutenberg' ), 'content' => "\n
\n

2.
" . __( 'Which treats of the first sally the ingenious Don Quixote made from home', 'gutenberg' ) . "

\n\n\n\n

" . __( 'These preliminaries settled, he did not care to put off any longer the execution of his design, urged on to it by the thought of all the world was losing by his delay, seeing what wrongs he intended to right, grievances to redress, injustices to repair, abuses to remove, and duties to discharge.', 'gutenberg' ) . "

\n
\n", - 'viewportWidth' => 1000, 'categories' => array( 'text' ), 'description' => _x( 'A heading preceded by a chapter number, and followed by a paragraph.', 'Block pattern description', 'gutenberg' ), ); diff --git a/lib/patterns/large-header-button.php b/lib/patterns/large-header-button.php index 1d71e5703f5b5f..75519963485e07 100644 --- a/lib/patterns/large-header-button.php +++ b/lib/patterns/large-header-button.php @@ -6,9 +6,8 @@ */ return array( - 'title' => __( 'Large header with a heading and a button ', 'gutenberg' ), - 'content' => "\n
\n
\n
\n
\n
\n\n\n\n
\n
\n\n\n\n

" . __( 'Thou hast seen', 'gutenberg' ) . '
' . __( 'nothing yet', 'gutenberg' ) . "

\n\n\n\n\n\n\n\n
\n
\n\n\n\n
\n
\n
\n
\n
\n", - 'viewportWidth' => 1000, - 'categories' => array( 'header' ), - 'description' => _x( 'A large hero section with a bright gradient background, a big heading and a filled button.', 'Block pattern description', 'gutenberg' ), + 'title' => __( 'Large header with a heading and a button ', 'gutenberg' ), + 'content' => "\n
\n
\n
\n
\n
\n\n\n\n
\n
\n\n\n\n

" . __( 'Thou hast seen
nothing yet', 'gutenberg' ) . "

\n\n\n\n\n\n\n\n
\n
\n\n\n\n
\n
\n
\n
\n
\n", + 'categories' => array( 'header' ), + 'description' => _x( 'A large hero section with a bright gradient background, a big heading and a filled button.', 'Block pattern description', 'gutenberg' ), ); diff --git a/lib/patterns/large-header.php b/lib/patterns/large-header.php index 0213bf8bfeb724..0583e1e6a03b2b 100644 --- a/lib/patterns/large-header.php +++ b/lib/patterns/large-header.php @@ -6,9 +6,8 @@ */ return array( - 'title' => __( 'Large header with a heading', 'gutenberg' ), - 'content' => "\n
\n

" . __( 'Don Quixote', 'gutenberg' ) . "

\n
\n", - 'viewportWidth' => 1000, - 'categories' => array( 'header' ), - 'description' => _x( 'A large hero section with an example background image and a heading in the center.', 'Block pattern description', 'gutenberg' ), + 'title' => __( 'Large header with a heading', 'gutenberg' ), + 'content' => "\n
\n

" . __( 'Don Quixote', 'gutenberg' ) . "

\n
\n", + 'categories' => array( 'header' ), + 'description' => _x( 'A large hero section with an example background image and a heading in the center.', 'Block pattern description', 'gutenberg' ), ); diff --git a/lib/patterns/text-three-columns-buttons.php b/lib/patterns/text-three-columns-buttons.php index 629c839b89bb88..8386be74b9c5bb 100644 --- a/lib/patterns/text-three-columns-buttons.php +++ b/lib/patterns/text-three-columns-buttons.php @@ -6,9 +6,8 @@ */ return array( - 'title' => __( 'Three columns of text with buttons', 'gutenberg' ), - 'categories' => array( 'columns' ), - 'content' => "\n
\n
\n
\n

" . __( 'Which treats of the character and pursuits of the famous Don Quixote of La Mancha.', 'gutenberg' ) . "

\n\n\n\n\n
\n\n\n\n
\n

" . __( 'Which treats of the first sally the ingenious Don Quixote made from home.', 'gutenberg' ) . "

\n\n\n\n\n
\n\n\n\n
\n

" . __( 'Wherein is related the droll way in which Don Quixote had himself dubbed a knight.', 'gutenberg' ) . "

\n\n\n\n\n
\n
\n
\n", - 'viewportWidth' => 1000, - 'description' => _x( 'Three small columns of text, each with an outlined button with rounded corners at the bottom.', 'Block pattern description', 'gutenberg' ), + 'title' => __( 'Three columns of text with buttons', 'gutenberg' ), + 'categories' => array( 'columns' ), + 'content' => "\n
\n
\n
\n

" . __( 'Which treats of the character and pursuits of the famous Don Quixote of La Mancha.', 'gutenberg' ) . "

\n\n\n\n\n
\n\n\n\n
\n

" . __( 'Which treats of the first sally the ingenious Don Quixote made from home.', 'gutenberg' ) . "

\n\n\n\n\n
\n\n\n\n
\n

" . __( 'Wherein is related the droll way in which Don Quixote had himself dubbed a knight.', 'gutenberg' ) . "

\n\n\n\n\n
\n
\n
\n", + 'description' => _x( 'Three small columns of text, each with an outlined button with rounded corners at the bottom.', 'Block pattern description', 'gutenberg' ), ); diff --git a/lib/patterns/two-buttons.php b/lib/patterns/two-buttons.php index 90564c4c30487d..8cd24997ce20ed 100644 --- a/lib/patterns/two-buttons.php +++ b/lib/patterns/two-buttons.php @@ -7,7 +7,7 @@ return array( 'title' => __( 'Two buttons', 'gutenberg' ), - 'content' => "\n\n", + 'content' => "\n\n", 'viewportWidth' => 500, 'categories' => array( 'buttons' ), 'description' => _x( 'Two buttons, one filled and one outlined, side by side.', 'Block pattern description', 'gutenberg' ), diff --git a/lib/rest-api.php b/lib/rest-api.php index 8ad865309167b1..30c44eb39d2452 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -61,9 +61,8 @@ function gutenberg_filter_oembed_result( $response, $handler, $request ) { * * @param WP_REST_Response $response The response object. * @param WP_Theme $theme Theme object used to create response. - * @param WP_REST_Request $request Request object. */ -function gutenberg_filter_rest_prepare_theme( $response, $theme, $request ) { +function gutenberg_filter_rest_prepare_theme( $response, $theme ) { $data = $response->get_data(); $fields = array_keys( $data ); @@ -125,7 +124,7 @@ function gutenberg_filter_rest_prepare_theme( $response, $theme, $request ) { $response->set_data( $data ); return $response; } -add_filter( 'rest_prepare_theme', 'gutenberg_filter_rest_prepare_theme', 10, 3 ); +add_filter( 'rest_prepare_theme', 'gutenberg_filter_rest_prepare_theme', 10, 2 ); /** * Registers the block directory. diff --git a/lib/template-loader.php b/lib/template-loader.php index b534db1637b2e4..b886a9f5ab58b6 100644 --- a/lib/template-loader.php +++ b/lib/template-loader.php @@ -5,40 +5,15 @@ * @package gutenberg */ -/** - * Return a list of all overrideable default template types. - * - * @see get_query_template - * - * @return string[] List of all overrideable default template types. - */ -function get_template_types() { - return array( - 'index', - '404', - 'archive', - 'author', - 'category', - 'tag', - 'taxonomy', - 'date', - 'embed', - 'home', - 'front-page', - 'privacy-policy', - 'page', - 'search', - 'single', - 'singular', - 'attachment', - ); -} - /** * Adds necessary filters to use 'wp_template' posts instead of theme template files. */ function gutenberg_add_template_loader_filters() { - foreach ( get_template_types() as $template_type ) { + if ( ! gutenberg_is_fse_theme() ) { + return; + } + + foreach ( gutenberg_get_template_type_slugs() as $template_type ) { if ( 'embed' === $template_type ) { // Skip 'embed' for now because it is not a regular template type. continue; } @@ -56,7 +31,7 @@ function gutenberg_add_template_loader_filters() { * @return string[] A list of template candidates, in descending order of priority. */ function get_template_hierarchy( $template_type ) { - if ( ! in_array( $template_type, get_template_types(), true ) ) { + if ( ! in_array( $template_type, gutenberg_get_template_type_slugs(), true ) ) { return array(); } @@ -89,13 +64,13 @@ function get_template_hierarchy( $template_type ) { function gutenberg_override_query_template( $template, $type, array $templates = array() ) { global $_wp_current_template_content; - $current_template = gutenberg_find_template_post_and_parts( $type, $templates ); + $current_template = gutenberg_resolve_template( $type, $templates ); if ( $current_template ) { - $_wp_current_template_content = empty( $current_template['template_post']->post_content ) ? __( 'Empty template.', 'gutenberg' ) : $current_template['template_post']->post_content; + $_wp_current_template_content = empty( $current_template->post_content ) ? __( 'Empty template.', 'gutenberg' ) : $current_template->post_content; if ( isset( $_GET['_wp-find-template'] ) ) { - wp_send_json_success( $current_template['template_post'] ); + wp_send_json_success( $current_template ); } } else { if ( 'index' === $type ) { @@ -120,93 +95,7 @@ function gutenberg_override_query_template( $template, $type, array $templates = } /** - * Recursively traverses a block tree, creating auto drafts - * for any encountered template parts without a fixed post. - * - * @access private - * - * @param array $block The root block to start traversing from. - * @return int[] A list of template parts IDs for the given block. - */ -function create_auto_draft_for_template_part_block( $block ) { - $template_part_ids = array(); - - if ( 'core/template-part' === $block['blockName'] && isset( $block['attrs']['slug'] ) ) { - if ( isset( $block['attrs']['postId'] ) ) { - // Template part is customized. - $template_part_id = $block['attrs']['postId']; - } else { - // A published post might already exist if this template part - // was customized elsewhere or if it's part of a customized - // template. We also check if an auto-draft was already created - // because preloading can make this run twice, so, different code - // paths can end up with different posts for the same template part. - // E.g. The server could send back post ID 1 to the client, preload, - // and create another auto-draft. So, if the client tries to resolve the - // post ID from the slug and theme, it won't match with what the server sent. - $template_part_query = new WP_Query( - array( - 'post_type' => 'wp_template_part', - 'post_status' => array( 'publish', 'auto-draft' ), - 'title' => $block['attrs']['slug'], - 'meta_key' => 'theme', - 'meta_value' => $block['attrs']['theme'], - 'posts_per_page' => 1, - 'no_found_rows' => true, - ) - ); - $template_part_post = $template_part_query->have_posts() ? $template_part_query->next_post() : null; - if ( $template_part_post && 'auto-draft' !== $template_part_post->post_status ) { - $template_part_id = $template_part_post->ID; - } else { - // Template part is not customized, get it from a file and make an auto-draft for it, unless one already exists - // and the underlying file hasn't changed. - $template_part_file_path = - get_stylesheet_directory() . '/block-template-parts/' . $block['attrs']['slug'] . '.html'; - if ( ! file_exists( $template_part_file_path ) ) { - if ( gutenberg_is_experiment_enabled( 'gutenberg-full-site-editing-demo' ) ) { - $template_part_file_path = - dirname( __FILE__ ) . '/demo-block-template-parts/' . $block['attrs']['slug'] . '.html'; - if ( ! file_exists( $template_part_file_path ) ) { - $template_part_file_path = false; - } - } else { - $template_part_file_path = false; - } - } - - if ( $template_part_file_path ) { - $file_contents = file_get_contents( $template_part_file_path ); - if ( $template_part_post && $template_part_post->post_content === $file_contents ) { - $template_part_id = $template_part_post->ID; - } else { - $template_part_id = wp_insert_post( - array( - 'post_content' => $file_contents, - 'post_title' => $block['attrs']['slug'], - 'post_status' => 'auto-draft', - 'post_type' => 'wp_template_part', - 'post_name' => $block['attrs']['slug'], - 'meta_input' => array( - 'theme' => $block['attrs']['theme'], - ), - ) - ); - } - } - } - } - $template_part_ids[ $block['attrs']['slug'] ] = $template_part_id; - } - - foreach ( $block['innerBlocks'] as $inner_block ) { - $template_part_ids = array_merge( $template_part_ids, create_auto_draft_for_template_part_block( $inner_block ) ); - } - return $template_part_ids; -} - -/** - * Return the correct 'wp_template' post and template part IDs for the current template. + * Return the correct 'wp_template' to render fot the request template type. * * Accepts an optional $template_hierarchy argument as a hint. * @@ -217,7 +106,7 @@ function create_auto_draft_for_template_part_block( $block ) { * @type int[] A list of template parts IDs for the template. * } */ -function gutenberg_find_template_post_and_parts( $template_type, $template_hierarchy = array() ) { +function gutenberg_resolve_template( $template_type, $template_hierarchy = array() ) { if ( ! $template_type ) { return null; } @@ -235,110 +124,38 @@ function gutenberg_find_template_post_and_parts( $template_type, $template_hiera $template_hierarchy ); - // Find most specific 'wp_template' post matching the hierarchy. + // Find all potential templates 'wp_template' post matching the hierarchy. $template_query = new WP_Query( array( 'post_type' => 'wp_template', - 'post_status' => 'publish', + 'post_status' => array( 'publish', 'auto-draft' ), 'post_name__in' => $slugs, 'orderby' => 'post_name__in', - 'posts_per_page' => 1, + 'posts_per_page' => -1, 'no_found_rows' => true, + 'tax_query' => array( + array( + 'taxonomy' => 'wp_theme', + 'field' => 'slug', + 'terms' => wp_get_theme()->get_stylesheet(), + ), + ), ) ); + $templates = $template_query->get_posts(); - $current_template_post = $template_query->have_posts() ? $template_query->next_post() : null; - + // Order these templates per slug priority. // Build map of template slugs to their priority in the current hierarchy. $slug_priorities = array_flip( $slugs ); - // See if there is a theme block template with higher priority than the resolved template post. - $higher_priority_block_template_path = null; - $higher_priority_block_template_priority = PHP_INT_MAX; - $block_template_files = gutenberg_get_template_paths(); - foreach ( $block_template_files as $path ) { - if ( ! isset( $slug_priorities[ basename( $path, '.html' ) ] ) ) { - continue; + usort( + $templates, + function ( $template_a, $template_b ) use ( $slug_priorities ) { + return $slug_priorities[ $template_a->post_name ] - $slug_priorities[ $template_b->post_name ]; } - $theme_block_template_priority = $slug_priorities[ basename( $path, '.html' ) ]; - if ( - $theme_block_template_priority < $higher_priority_block_template_priority && - ( empty( $current_template_post ) || $theme_block_template_priority < $slug_priorities[ $current_template_post->post_name ] ) - ) { - $higher_priority_block_template_path = $path; - $higher_priority_block_template_priority = $theme_block_template_priority; - } - } - - // If there is, use it instead. - if ( isset( $higher_priority_block_template_path ) ) { - $post_name = basename( $higher_priority_block_template_path, '.html' ); - $file_contents = file_get_contents( $higher_priority_block_template_path ); - $current_template_post = array( - 'post_content' => $file_contents, - 'post_title' => $post_name, - 'post_status' => 'auto-draft', - 'post_type' => 'wp_template', - 'post_name' => $post_name, - ); - if ( is_admin() || defined( 'REST_REQUEST' ) ) { - $template_query = new WP_Query( - array( - 'post_type' => 'wp_template', - 'post_status' => 'auto-draft', - 'name' => $post_name, - 'posts_per_page' => 1, - 'no_found_rows' => true, - ) - ); - $current_template_post = $template_query->have_posts() ? $template_query->next_post() : $current_template_post; - - // Only create auto-draft of block template for editing - // in admin screens, when necessary, because the underlying - // file has changed. - if ( is_array( $current_template_post ) || $current_template_post->post_content !== $file_contents ) { - if ( ! is_array( $current_template_post ) ) { - $current_template_post->post_content = $file_contents; - } - $current_template_post = get_post( - wp_insert_post( $current_template_post ) - ); - } - } else { - $current_template_post = new WP_Post( - (object) $current_template_post - ); - } - } - - // If we haven't found any template post by here, it means that this theme doesn't even come with a fallback - // `index.html` block template. We create one so that people that are trying to access the editor are greeted - // with a blank page rather than an error. - if ( ! $current_template_post && ( is_admin() || defined( 'REST_REQUEST' ) ) ) { - $current_template_post = array( - 'post_title' => 'index', - 'post_status' => 'auto-draft', - 'post_type' => 'wp_template', - 'post_name' => 'index', - ); - $current_template_post = get_post( - wp_insert_post( $current_template_post ) - ); - } + ); - if ( $current_template_post ) { - $template_part_ids = array(); - if ( is_admin() || defined( 'REST_REQUEST' ) ) { - foreach ( parse_blocks( $current_template_post->post_content ) as $block ) { - $template_part_ids = array_merge( $template_part_ids, create_auto_draft_for_template_part_block( $block ) ); - } - } - return array( - 'template_post' => $current_template_post, - 'template_part_ids' => $template_part_ids, - ); - } - return null; + return count( $templates ) ? $templates[0] : null; } /** @@ -401,37 +218,6 @@ function gutenberg_strip_php_suffix( $template_file ) { return preg_replace( '/\.php$/', '', $template_file ); } -/** - * Extends default editor settings to enable template and template part editing. - * - * @param array $settings Default editor settings. - * - * @return array Filtered editor settings. - */ -function gutenberg_template_loader_filter_block_editor_settings( $settings ) { - global $post; - - if ( ! $post ) { - return $settings; - } - - // If this is the Site Editor, auto-drafts for template parts have already been generated - // through `filter_rest_wp_template_part_query`, when called via the REST API. - if ( isset( $settings['editSiteInitialState'] ) ) { - return $settings; - } - - // Otherwise, create template part auto-drafts for the edited post. - $post = get_post(); - foreach ( parse_blocks( $post->post_content ) as $block ) { - create_auto_draft_for_template_part_block( $block ); - } - - // TODO: Set editing mode and current template ID for editing modes support. - return $settings; -} -add_filter( 'block_editor_settings', 'gutenberg_template_loader_filter_block_editor_settings' ); - /** * Removes post details from block context when rendering a block template. * diff --git a/lib/template-parts.php b/lib/template-parts.php index 9fe0fa530cf579..464200f66d000a 100644 --- a/lib/template-parts.php +++ b/lib/template-parts.php @@ -9,6 +9,10 @@ * Registers block editor 'wp_template_part' post type. */ function gutenberg_register_template_part_post_type() { + if ( ! gutenberg_is_fse_theme() ) { + return; + } + $labels = array( 'name' => __( 'Template Parts', 'gutenberg' ), 'singular_name' => __( 'Template Part', 'gutenberg' ), @@ -45,22 +49,13 @@ function gutenberg_register_template_part_post_type() { 'supports' => array( 'title', 'slug', + 'excerpt', 'editor', 'revisions', - 'custom-fields', ), ); - $meta_args = array( - 'object_subtype' => 'wp_template_part', - 'type' => 'string', - 'description' => 'The theme that provided the template part, if any.', - 'single' => true, - 'show_in_rest' => true, - ); - register_post_type( 'wp_template_part', $args ); - register_meta( 'post', 'theme', $meta_args ); } add_action( 'init', 'gutenberg_register_template_part_post_type' ); @@ -88,6 +83,10 @@ function gutenberg_filter_wp_template_part_wp_unique_post_slug( $slug, $post_ID, * Fixes the label of the 'wp_template_part' admin menu entry. */ function gutenberg_fix_template_part_admin_menu_entry() { + if ( ! gutenberg_is_fse_theme() ) { + return; + } + global $submenu; if ( ! isset( $submenu['themes.php'] ) ) { return; @@ -105,54 +104,20 @@ function gutenberg_fix_template_part_admin_menu_entry() { } add_action( 'admin_menu', 'gutenberg_fix_template_part_admin_menu_entry' ); -/** - * Filters the 'wp_template_part' post type columns in the admin list table. - * - * @param array $columns Columns to display. - * @return array Filtered $columns. - */ -function gutenberg_filter_template_part_list_table_columns( array $columns ) { - $columns['slug'] = __( 'Slug', 'gutenberg' ); - if ( isset( $columns['date'] ) ) { - unset( $columns['date'] ); - } - return $columns; -} -add_filter( 'manage_wp_template_part_posts_columns', 'gutenberg_filter_template_part_list_table_columns' ); +// Customize the `wp_template` admin list. +add_filter( 'manage_wp_template_part_posts_columns', 'gutenberg_templates_lists_custom_columns' ); +add_action( 'manage_wp_template_part_posts_custom_column', 'gutenberg_render_templates_lists_custom_column', 10, 2 ); +add_filter( 'views_edit-wp_template_part', 'gutenberg_filter_templates_edit_views' ); /** - * Renders column content for the 'wp_template_part' post type list table. - * - * @param string $column_name Column name to render. - * @param int $post_id Post ID. - */ -function gutenberg_render_template_part_list_table_column( $column_name, $post_id ) { - if ( 'slug' !== $column_name ) { - return; - } - $post = get_post( $post_id ); - echo esc_html( $post->post_name ); -} -add_action( 'manage_wp_template_part_posts_custom_column', 'gutenberg_render_template_part_list_table_column', 10, 2 ); - - -/** - * Filter for adding a `resolved`, a `template`, and a `theme` parameter to `wp_template_part` queries. + * Filter for adding and a `theme` parameter to `wp_template_part` queries. * * @param array $query_params The query parameters. * @return array Filtered $query_params. */ function filter_rest_wp_template_part_collection_params( $query_params ) { $query_params += array( - 'resolved' => array( - 'description' => __( 'Whether to filter for resolved template parts.', 'gutenberg' ), - 'type' => 'boolean', - ), - 'template' => array( - 'description' => __( 'The template slug for the template that the template part is used by.', 'gutenberg' ), - 'type' => 'string', - ), - 'theme' => array( + 'theme' => array( 'description' => __( 'The theme slug for the theme that created the template part.', 'gutenberg' ), 'type' => 'string', ), @@ -169,90 +134,15 @@ function filter_rest_wp_template_part_collection_params( $query_params ) { * @return array Filtered $args. */ function filter_rest_wp_template_part_query( $args, $request ) { - /** - * Unlike `filter_rest_wp_template_query`, we resolve queries also if there's only a `template` argument set. - * The difference is that in the case of templates, we can use the `slug` field that already exists (as part - * of the entities endpoint, wheras for template parts, we have to register the extra `template` argument), - * so we need the `resolved` flag to convey the different semantics (only return 'resolved' templates that match - * the `slug` vs return _all_ templates that match it (e.g. including all auto-drafts)). - * - * A template parts query with a `template` arg but not a `resolved` one is conceivable, but probably wouldn't be - * very useful: It'd be all template parts for all templates matching that `template` slug (including auto-drafts etc). - * - * @see filter_rest_wp_template_query - * @see filter_rest_wp_template_part_collection_params - * @see https://github.com/WordPress/gutenberg/pull/21878#discussion_r436961706 - */ - if ( $request['resolved'] || $request['template'] ) { - $template_part_ids = array( 0 ); // Return nothing by default (the 0 is needed for `post__in`). - $template_types = $request['template'] ? array( $request['template'] ) : get_template_types(); - - foreach ( $template_types as $template_type ) { - // Skip 'embed' for now because it is not a regular template type. - if ( in_array( $template_type, array( 'embed' ), true ) ) { - continue; - } - - $current_template = gutenberg_find_template_post_and_parts( $template_type ); - if ( isset( $current_template ) ) { - $template_part_ids = $template_part_ids + $current_template['template_part_ids']; - } - } - $args['post__in'] = $template_part_ids; - $args['post_status'] = array( 'publish', 'auto-draft' ); - } - if ( $request['theme'] ) { - $meta_query = isset( $args['meta_query'] ) ? $args['meta_query'] : array(); - $meta_query[] = array( - 'key' => 'theme', - 'value' => $request['theme'], + $tax_query = isset( $args['tax_query'] ) ? $args['tax_query'] : array(); + $tax_query[] = array( + 'taxonomy' => 'wp_theme', + 'field' => 'slug', + 'terms' => $request['theme'], ); - // Ensure auto-drafts of all theme supplied template parts are created. - if ( wp_get_theme()->stylesheet === $request['theme'] ) { - /** - * Finds all nested template part file paths in a theme's directory. - * - * @param string $base_directory The theme's file path. - * @return array $path_list A list of paths to all template part files. - */ - function get_template_part_paths( $base_directory ) { - $path_list = array(); - if ( file_exists( $base_directory . '/block-template-parts' ) ) { - $nested_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory . '/block-template-parts' ) ); - $nested_html_files = new RegexIterator( $nested_files, '/^.+\.html$/i', RecursiveRegexIterator::GET_MATCH ); - foreach ( $nested_html_files as $path => $file ) { - $path_list[] = $path; - } - } - return $path_list; - } - - // Get file paths for all theme supplied template parts. - $template_part_files = get_template_part_paths( get_stylesheet_directory() ); - if ( is_child_theme() ) { - $template_part_files = array_merge( $template_part_files, get_template_part_paths( get_template_directory() ) ); - } - // Build and save each template part. - foreach ( $template_part_files as $template_part_file ) { - $content = file_get_contents( $template_part_file ); - // Infer slug from filepath. - $slug = substr( - $template_part_file, - // Starting position of slug. - strpos( $template_part_file, 'block-template-parts/' ) + 21, - // Subtract ending '.html'. - -5 - ); - // Wrap content with the template part block, parse, and create auto-draft. - $template_part_string = '' . $content . ''; - $template_part_block = parse_blocks( $template_part_string )[0]; - create_auto_draft_for_template_part_block( $template_part_block ); - } - }; - - $args['meta_query'] = $meta_query; + $args['tax_query'] = $tax_query; } return $args; diff --git a/lib/templates-sync.php b/lib/templates-sync.php new file mode 100644 index 00000000000000..b5c1076f49ce8c --- /dev/null +++ b/lib/templates-sync.php @@ -0,0 +1,188 @@ + $post_type, + 'post_status' => array( 'publish', 'auto-draft' ), + 'post_name__in' => array( $slug ), + 'tax_query' => array( + array( + 'taxonomy' => 'wp_theme', + 'field' => 'slug', + 'terms' => $theme, + ), + ), + 'posts_per_page' => 1, + 'no_found_rows' => true, + ) + ); + $post = $template_query->have_posts() ? $template_query->next_post() : null; + if ( ! $post ) { + $template_post = array( + 'post_content' => $content, + 'post_title' => $slug, + 'post_status' => 'auto-draft', + 'post_type' => $post_type, + 'post_name' => $slug, + 'tax_input' => array( 'wp_theme' => array( $theme, '_wp_file_based' ) ), + ); + + if ( 'wp_template' === $post_type ) { + $default_template_types = gutenberg_get_default_template_types(); + if ( isset( $default_template_types[ $slug ] ) ) { + $template_post['post_title'] = $default_template_types[ $slug ]['title']; + $template_post['post_excerpt'] = $default_template_types[ $slug ]['description']; + } + } + + wp_insert_post( $template_post ); + } elseif ( 'auto-draft' === $post->post_status && $content !== $post->post_content ) { + // If the template already exists, but it was never changed by the user + // and the template file content changed then update the content of auto-draft. + $post->post_content = $content; + wp_insert_post( $post ); + } +} + +/** + * Finds all nested template part file paths in a theme's directory. + * + * @access private + * + * @param string $base_directory The theme's file path. + * @return array $path_list A list of paths to all template part files. + */ +function _gutenberg_get_template_paths( $base_directory ) { + $path_list = array(); + if ( file_exists( $base_directory ) ) { + $nested_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory ) ); + $nested_html_files = new RegexIterator( $nested_files, '/^.+\.html$/i', RecursiveRegexIterator::GET_MATCH ); + foreach ( $nested_html_files as $path => $file ) { + $path_list[] = $path; + } + } + return $path_list; +} + +/** + * Create the template parts auto-drafts for the current theme. + * + * @access private + * @internal + * + * @param string $template_type The template type (template or template-part). + */ +function _gutenberg_synchronize_theme_templates( $template_type ) { + $template_post_types = array( + 'template' => 'wp_template', + 'template-part' => 'wp_template_part', + ); + $template_base_paths = array( + 'template' => 'block-templates', + 'template-part' => 'block-template-parts', + ); + $themes = array( + get_stylesheet() => get_stylesheet_directory(), + get_template() => get_template_directory(), + ); + + // Get file paths for all theme supplied template that changed since last check. + $template_files = array(); + $option_name = 'gutenberg_last_synchronize_theme_' . $template_type . '_checks'; + $last_checks = get_option( $option_name, array() ); + $current_time = time(); + foreach ( $themes as $theme_slug => $theme_dir ) { + $last_check = isset( $last_checks[ $theme_slug ] ) ? $last_checks[ $theme_slug ] : 0; + + $theme_template_files = _gutenberg_get_template_paths( $theme_dir . '/' . $template_base_paths[ $template_type ] ); + foreach ( $theme_template_files as $template_file ) { + if ( filemtime( $template_file ) > $last_check ) { + $template_files[] = array( + 'path' => $template_file, + 'theme' => $theme_slug, + ); + } + } + + $last_checks[ $theme_slug ] = $current_time; + } + + // Build and save each template part. + foreach ( $template_files as $template_file ) { + $path = $template_file['path']; + $theme = $template_file['theme']; + $template_base_path = $template_base_paths[ $template_type ]; + + $content = file_get_contents( $path ); + $slug = substr( + $path, + // Starting position of slug. + strpos( $path, $template_base_path . DIRECTORY_SEPARATOR ) + 1 + strlen( $template_base_path ), + // Subtract ending '.html'. + -5 + ); + _gutenberg_create_auto_draft_for_template( $template_post_types[ $template_type ], $slug, $theme, $content ); + } + + update_option( $option_name, $last_checks ); +} + +/** + * Synchronize changed template and template part files after WordPress is loaded + */ +function gutenberg_synchronize_theme_templates_on_load() { + if ( ! gutenberg_is_fse_theme() ) { + return; + } + + _gutenberg_synchronize_theme_templates( 'template-part' ); + _gutenberg_synchronize_theme_templates( 'template' ); +} +add_action( 'wp_loaded', 'gutenberg_synchronize_theme_templates_on_load' ); + +/** + * Clears synchronization last check timestamps. + */ +function gutenberg_clear_synchronize_last_checks() { + update_option( 'gutenberg_last_synchronize_theme_template_checks', array() ); + update_option( 'gutenberg_last_synchronize_theme_template-part_checks', array() ); +} + +// Clear synchronization last check timestamps after trashing a template or template part. +add_action( 'trash_wp_template', 'gutenberg_clear_synchronize_last_checks' ); +add_action( 'trash_wp_template_part', 'gutenberg_clear_synchronize_last_checks' ); + +/** + * Clear synchronization last check timestamps after deleting a template or template part. + * + * @param int $post_id ID of the deleted post. + * @param WP_Post $post WP_Post instance of the deleted post. + */ +function gutenberg_clear_synchronize_last_checks_after_delete( $post_id, $post ) { + if ( 'wp_template' === $post->post_type || 'wp_template_part' === $post->post_type ) { + gutenberg_clear_synchronize_last_checks(); + } +} +add_action( 'after_delete_post', 'gutenberg_clear_synchronize_last_checks_after_delete', 10, 2 ); diff --git a/lib/templates.php b/lib/templates.php index d0c9075b8db848..aa085c0d54d125 100644 --- a/lib/templates.php +++ b/lib/templates.php @@ -21,12 +21,6 @@ function gutenberg_get_template_paths() { $block_template_files = array_merge( $block_template_files, $child_block_template_files ); } - if ( gutenberg_is_experiment_enabled( 'gutenberg-full-site-editing-demo' ) ) { - $demo_block_template_files = glob( dirname( __FILE__ ) . '/demo-block-templates/*.html' ); - $demo_block_template_files = is_array( $demo_block_template_files ) ? $demo_block_template_files : array(); - $block_template_files = array_merge( $block_template_files, $demo_block_template_files ); - } - return $block_template_files; } @@ -34,6 +28,10 @@ function gutenberg_get_template_paths() { * Registers block editor 'wp_template' post type. */ function gutenberg_register_template_post_type() { + if ( ! gutenberg_is_fse_theme() ) { + return; + } + $labels = array( 'name' => __( 'Templates', 'gutenberg' ), 'singular_name' => __( 'Template', 'gutenberg' ), @@ -71,6 +69,7 @@ function gutenberg_register_template_post_type() { 'supports' => array( 'title', 'slug', + 'excerpt', 'editor', 'revisions', ), @@ -80,6 +79,49 @@ function gutenberg_register_template_post_type() { } add_action( 'init', 'gutenberg_register_template_post_type' ); +/** + * Registers block editor 'wp_theme' taxonomy. + */ +function gutenberg_register_wp_theme_taxonomy() { + if ( ! gutenberg_is_fse_theme() ) { + return; + } + + register_taxonomy( + 'wp_theme', + array( 'wp_template', 'wp_template_part' ), + array( + 'public' => false, + 'hierarchical' => false, + 'labels' => array( + 'name' => __( 'Themes', 'gutenberg' ), + 'singular_name' => __( 'Theme', 'gutenberg' ), + ), + 'query_var' => false, + 'rewrite' => false, + 'show_ui' => false, + '_builtin' => true, + 'show_in_nav_menus' => false, + 'show_in_rest' => true, + ) + ); +} +add_action( 'init', 'gutenberg_register_wp_theme_taxonomy' ); + +/** + * Automatically set the theme meta for templates. + * + * @param array $post_id Template ID. + */ +function gutenberg_set_template_and_template_part_post_theme( $post_id ) { + $themes = wp_get_post_terms( $post_id, 'wp_theme' ); + if ( ! $themes ) { + wp_set_post_terms( $post_id, array( wp_get_theme()->get_stylesheet() ), 'wp_theme', true ); + } +} +add_action( 'save_post_wp_template', 'gutenberg_set_template_and_template_part_post_theme', 10, 3 ); +add_action( 'save_post_wp_template_part', 'gutenberg_set_template_and_template_part_post_theme', 10, 3 ); + /** * Filters the capabilities of a user to conditionally grant them capabilities for managing 'wp_template' posts. * @@ -130,6 +172,9 @@ function gutenberg_filter_wp_template_wp_unique_post_slug( $slug, $post_ID, $pos * Fixes the label of the 'wp_template' admin menu entry. */ function gutenberg_fix_template_admin_menu_entry() { + if ( ! gutenberg_is_fse_theme() ) { + return; + } global $submenu; if ( ! isset( $submenu['themes.php'] ) ) { return; @@ -147,35 +192,10 @@ function gutenberg_fix_template_admin_menu_entry() { } add_action( 'admin_menu', 'gutenberg_fix_template_admin_menu_entry' ); -/** - * Filters the 'wp_template' post type columns in the admin list table. - * - * @param array $columns Columns to display. - * @return array Filtered $columns. - */ -function gutenberg_filter_template_list_table_columns( array $columns ) { - $columns['slug'] = __( 'Slug', 'gutenberg' ); - if ( isset( $columns['date'] ) ) { - unset( $columns['date'] ); - } - return $columns; -} -add_filter( 'manage_wp_template_posts_columns', 'gutenberg_filter_template_list_table_columns' ); - -/** - * Renders column content for the 'wp_template' post type list table. - * - * @param string $column_name Column name to render. - * @param int $post_id Post ID. - */ -function gutenberg_render_template_list_table_column( $column_name, $post_id ) { - if ( 'slug' !== $column_name ) { - return; - } - $post = get_post( $post_id ); - echo esc_html( $post->post_name ); -} -add_action( 'manage_wp_template_posts_custom_column', 'gutenberg_render_template_list_table_column', 10, 2 ); +// Customize the `wp_template` admin list. +add_filter( 'manage_wp_template_posts_columns', 'gutenberg_templates_lists_custom_columns' ); +add_action( 'manage_wp_template_posts_custom_column', 'gutenberg_render_templates_lists_custom_column', 10, 2 ); +add_filter( 'views_edit-wp_template', 'gutenberg_filter_templates_edit_views' ); /** * Filter for adding a `resolved` parameter to `wp_template` queries. @@ -202,16 +222,9 @@ function filter_rest_wp_template_collection_params( $query_params ) { * @return array Filtered $args. */ function filter_rest_wp_template_query( $args, $request ) { - // Create auto-drafts for each theme template files. - $block_template_files = gutenberg_get_template_paths(); - foreach ( $block_template_files as $path ) { - $template_type = basename( $path, '.html' ); - gutenberg_find_template_post_and_parts( $template_type, array( $template_type ) ); - } - if ( $request['resolved'] ) { $template_ids = array( 0 ); // Return nothing by default (the 0 is needed for `post__in`). - $template_types = $request['slug'] ? $request['slug'] : get_template_types(); + $template_types = $request['slug'] ? $request['slug'] : gutenberg_get_template_type_slugs(); foreach ( $template_types as $template_type ) { // Skip 'embed' for now because it is not a regular template type. @@ -219,9 +232,9 @@ function filter_rest_wp_template_query( $args, $request ) { continue; } - $current_template = gutenberg_find_template_post_and_parts( $template_type ); - if ( isset( $current_template ) ) { - $template_ids[] = $current_template['template_post']->ID; + $current_template = gutenberg_resolve_template( $template_type ); + if ( $current_template ) { + $template_ids[] = $current_template->ID; } } $args['post__in'] = $template_ids; @@ -231,3 +244,42 @@ function filter_rest_wp_template_query( $args, $request ) { return $args; } add_filter( 'rest_wp_template_query', 'filter_rest_wp_template_query', 99, 2 ); + +/** + * Filters the post data for a response. + * + * @param WP_REST_Response $response The response object. + * @return WP_REST_Response + */ +function filter_rest_prepare_add_wp_theme_slug_and_file_based( $response ) { + if ( isset( $response->data ) && is_array( $response->data ) && isset( $response->data['id'] ) ) { + $response->data['wp_theme_slug'] = false; + + // Get the wp_theme terms. + $wp_themes = wp_get_post_terms( $response->data['id'], 'wp_theme' ); + + // If a theme is assigned, add it to the REST response. + if ( $wp_themes && is_array( $wp_themes ) ) { + $wp_theme_slugs = array_column( $wp_themes, 'slug' ); + + $file_based = in_array( '_wp_file_based', $wp_theme_slugs, true ); + $response->data['file_based'] = $file_based; + + $theme_slug = array_values( + array_filter( + $wp_theme_slugs, + function( $slug ) { + return '_wp_file_based' !== $slug; + } + ) + ); + if ( $theme_slug ) { + $response->data['wp_theme_slug'] = $theme_slug[0]; + } + } + } + + return $response; +} +add_filter( 'rest_prepare_wp_template', 'filter_rest_prepare_add_wp_theme_slug_and_file_based' ); +add_filter( 'rest_prepare_wp_template_part', 'filter_rest_prepare_add_wp_theme_slug_and_file_based' ); diff --git a/lib/utils.php b/lib/utils.php index b5cdef7f999a99..216a444239a823 100644 --- a/lib/utils.php +++ b/lib/utils.php @@ -31,3 +31,63 @@ function gutenberg_experimental_get( $array, $path, $default = array() ) { } return $array; } + +/** + * Sets an array in depth based on a path of keys. + * + * It is the PHP equivalent of JavaScript's `lodash.set()` and mirroring it may help other components + * retain some symmetry between client and server implementations. + * + * Example usage: + * + * $array = array(); + * _wp_array_set( $array, array( 'a', 'b', 'c', 1 ); + * $array becomes: + * array( + * 'a' => array( + * 'b' => array( + * 'c' => 1, + * ), + * ), + * ); + * + * @param array $array An array that we want to mutate to include a specific value in a path. + * @param array $path An array of keys describing the path that we want to mutate. + * @param mixed $value The value that will be set. + */ +function gutenberg_experimental_set( &$array, $path, $value = null ) { + // Confirm $array is valid. + if ( ! is_array( $array ) ) { + return; + } + + // Confirm $path is valid. + if ( ! is_array( $path ) ) { + return; + } + $path_length = count( $path ); + if ( 0 === $path_length ) { + return; + } + foreach ( $path as $path_element ) { + if ( + ! is_string( $path_element ) && ! is_integer( $path_element ) && + ! is_null( $path_element ) + ) { + return; + } + } + + $i; + for ( $i = 0; $i < $path_length - 1; ++$i ) { + $path_element = $path[ $i ]; + if ( + ! array_key_exists( $path_element, $array ) || + ! is_array( $array[ $path_element ] ) + ) { + $array[ $path_element ] = array(); + } + $array = &$array[ $path_element ]; + } + $array[ $path[ $i ] ] = $value; +} diff --git a/lib/widgets-page.php b/lib/widgets-page.php index f6d31ca5b79b04..d31b814c72e475 100644 --- a/lib/widgets-page.php +++ b/lib/widgets-page.php @@ -43,37 +43,8 @@ function gutenberg_widgets_init( $hook ) { $initializer_name = 'initialize'; - // Media settings. - $max_upload_size = wp_max_upload_size(); - if ( ! $max_upload_size ) { - $max_upload_size = 0; - } - - /** This filter is documented in wp-admin/includes/media.php */ - $image_size_names = apply_filters( - 'image_size_names_choose', - array( - 'thumbnail' => __( 'Thumbnail', 'gutenberg' ), - 'medium' => __( 'Medium', 'gutenberg' ), - 'large' => __( 'Large', 'gutenberg' ), - 'full' => __( 'Full Size', 'gutenberg' ), - ) - ); - - $available_image_sizes = array(); - foreach ( $image_size_names as $image_size_slug => $image_size_name ) { - $available_image_sizes[] = array( - 'slug' => $image_size_slug, - 'name' => $image_size_name, - ); - } - $settings = array_merge( - array( - 'imageSizes' => $available_image_sizes, - 'isRTL' => is_rtl(), - 'maxUploadFileSize' => $max_upload_size, - ), + gutenberg_get_common_block_editor_settings(), gutenberg_get_legacy_widget_settings() ); @@ -109,7 +80,7 @@ function gutenberg_widgets_init( $hook ) { wp.editWidgets.%s( "widgets-editor", %s ); } );', $initializer_name, - wp_json_encode( gutenberg_experiments_editor_settings( $settings ) ) + wp_json_encode( $settings ) ) ); @@ -143,3 +114,15 @@ function gutenberg_widgets_editor_load_block_editor_scripts_and_styles( $is_bloc add_filter( 'should_load_block_editor_scripts_and_styles', 'gutenberg_widgets_editor_load_block_editor_scripts_and_styles' ); +/** + * Show responsive embeds correctly on the widgets screen by adding the wp-embed-responsive class. + * + * @param string $classes existing admin body classes. + * + * @return string admin body classes including the wp-embed-responsive class. + */ +function gutenberg_widgets_editor_add_responsive_embed_body_class( $classes ) { + return "$classes wp-embed-responsive"; +} + +add_filter( 'admin_body_class', 'gutenberg_widgets_editor_add_responsive_embed_body_class' ); diff --git a/lib/widgets.php b/lib/widgets.php index f87867b2b48210..4236a66413ba67 100644 --- a/lib/widgets.php +++ b/lib/widgets.php @@ -200,7 +200,7 @@ function gutenberg_get_legacy_widget_settings() { $settings['availableLegacyWidgets'] = $available_legacy_widgets; - return gutenberg_experiments_editor_settings( $settings ); + return $settings; } /** @@ -278,7 +278,7 @@ function gutenberg_load_widget_preview_if_requested() { current_user_can( 'edit_theme_options' ) ) { define( 'IFRAME_REQUEST', true ); - require_once dirname( __FILE__ ) . '/widget-preview-template.php'; + require_once __DIR__ . '/widget-preview-template.php'; exit; } } diff --git a/package-lock.json b/package-lock.json index 26a209082fb9b9..05f4f23b5ce8c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "9.2.0-rc.1", + "version": "9.6.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -20,46 +20,50 @@ "@octokit/rest": "^16.15.0" } }, - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/compat-data": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.0.tgz", - "integrity": "sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g==", + "@axe-core/puppeteer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@axe-core/puppeteer/-/puppeteer-4.0.0.tgz", + "integrity": "sha512-APFc2iRmrmUfqdBUevjsMxJliDudWPwvCU5Pzw2uKZ0sMVtW3rQuElTcfJCHFq8KOZSSO0BPD+lK+pXOx1ta5Q==", "dev": true, "requires": { - "browserslist": "^4.9.1", - "invariant": "^2.2.4", - "semver": "^5.5.0" + "axe-core": "^4.0.1" }, "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "axe-core": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.0.2.tgz", + "integrity": "sha512-arU1h31OGFu+LPrOLGZ7nB45v940NMDMEJeNmbutu57P+UFDVnkZg3e+J1I2HJRZ9hT7gO8J91dn/PMrAiKakA==", "dev": true } } }, + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/compat-data": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.7.tgz", + "integrity": "sha512-YaxPMGs/XIWtYqrdEOZOCPsVWfEoriXopnsz3/i7apYPXQ3698UFhS6dVT1KN5qOsWmVgw/FOrmQgpRaZayGsw==", + "dev": true + }, "@babel/core": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", - "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", + "version": "7.12.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", + "integrity": "sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==", "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.6", - "@babel/helper-module-transforms": "^7.11.0", - "@babel/helpers": "^7.10.4", - "@babel/parser": "^7.11.5", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.11.5", - "@babel/types": "^7.11.5", + "@babel/generator": "^7.12.5", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.5", + "@babel/parser": "^7.12.7", + "@babel/template": "^7.12.7", + "@babel/traverse": "^7.12.9", + "@babel/types": "^7.12.7", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", @@ -70,150 +74,6 @@ "source-map": "^0.5.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/generator": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", - "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", - "requires": { - "@babel/types": "^7.11.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", - "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", - "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-module-transforms": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", - "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", - "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-simple-access": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/template": "^7.10.4", - "@babel/types": "^7.11.0", - "lodash": "^4.17.19" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", - "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-replace-supers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", - "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", - "requires": { - "@babel/helper-member-expression-to-functions": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-simple-access": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", - "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", - "requires": { - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", - "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" - }, - "@babel/helpers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", - "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", - "requires": { - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", - "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==" - }, - "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/types": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", - "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, "convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -223,11 +83,11 @@ } }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "json5": { @@ -238,11 +98,6 @@ "minimist": "^1.2.5" } }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -256,69 +111,63 @@ } }, "@babel/generator": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", - "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.5.tgz", + "integrity": "sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==", "requires": { - "@babel/types": "^7.9.0", + "@babel/types": "^7.12.5", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", - "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", + "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz", - "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", + "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", "requires": { - "@babel/helper-explode-assignable-expression": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/helper-explode-assignable-expression": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-builder-react-jsx": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.9.0.tgz", - "integrity": "sha512-weiIo4gaoGgnhff54GQ3P5wsUQmnSwpkvU0r6ZHq6TzoSzKy4JxHEgnxNytaKbov2a9z/CVNyzliuCOUPEX3Jw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.4.tgz", + "integrity": "sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg==", "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/types": "^7.9.0" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-builder-react-jsx-experimental": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.9.0.tgz", - "integrity": "sha512-3xJEiyuYU4Q/Ar9BsHisgdxZsRlsShMe90URZ0e6przL26CCs8NJbDoxH94kKT17PcxlMhsCAwZd90evCo26VQ==", + "version": "7.12.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.12.4.tgz", + "integrity": "sha512-AjEa0jrQqNk7eDQOo0pTfUOwQBMF+xVqrausQwT9/rTKy0g04ggFNaJpaE09IQMn9yExluigWMJcj0WC7bq+Og==", "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-module-imports": "^7.8.3", - "@babel/types": "^7.9.0" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-module-imports": "^7.12.1", + "@babel/types": "^7.12.1" } }, "@babel/helper-compilation-targets": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz", - "integrity": "sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.5.tgz", + "integrity": "sha512-+qH6NrscMolUlzOYngSBMIOQpKUGPPsc61Bu5W10mg84LxZ7cmvnBHzARKbDoFxVvqqAbj6Tg6N7bSrWSPXMyw==", "dev": true, "requires": { - "@babel/compat-data": "^7.8.6", - "browserslist": "^4.9.1", - "invariant": "^2.2.4", - "levenary": "^1.1.1", + "@babel/compat-data": "^7.12.5", + "@babel/helper-validator-option": "^7.12.1", + "browserslist": "^4.14.5", "semver": "^5.5.0" }, "dependencies": { - "leven": { - "version": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" - }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -328,226 +177,214 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.6.tgz", - "integrity": "sha512-klTBDdsr+VFFqaDHm5rR69OpEQtO2Qv8ECxHS1mNhJJvaHArR6a1xTf5K/eZW7eZpJbhCx3NW1Yt/sKsLXLblg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz", + "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==", "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-split-export-declaration": "^7.8.3" + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.12.1", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.10.4" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz", - "integrity": "sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.7.tgz", + "integrity": "sha512-idnutvQPdpbduutvi3JVfEgcVIHooQnhvhx0Nk9isOINOIGYkZea1Pk2JlJRiUnMefrlvr0vkByATBY/mB4vjQ==", "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-regex": "^7.8.3", - "regexpu-core": "^4.7.0" + "@babel/helper-annotate-as-pure": "^7.10.4", + "regexpu-core": "^4.7.1" + }, + "dependencies": { + "regexpu-core": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", + "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + } + } } }, "@babel/helper-define-map": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz", - "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", + "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/types": "^7.8.3", - "lodash": "^4.17.13" + "@babel/helper-function-name": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" } }, "@babel/helper-explode-assignable-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz", - "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz", + "integrity": "sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==", "requires": { - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/types": "^7.12.1" } }, "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-hoist-variables": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz", - "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", + "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", - "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", + "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.12.7" } }, "@babel/helper-module-imports": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", - "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", + "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.12.5" } }, "@babel/helper-module-transforms": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", - "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", + "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-simple-access": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.6", - "@babel/types": "^7.9.0", - "lodash": "^4.17.13" + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-simple-access": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/helper-validator-identifier": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "lodash": "^4.17.19" } }, "@babel/helper-optimise-call-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", - "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.7.tgz", + "integrity": "sha512-I5xc9oSJ2h59OwyUqjv95HRyzxj53DAubUERgQMrpcCEYQyToeHA+NEcUEsVWB4j53RDeskeBJ0SgRAYHDBckw==", "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.12.7" } }, "@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" - }, - "@babel/helper-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz", - "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==", - "requires": { - "lodash": "^4.17.13" - } + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" }, "@babel/helper-remap-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz", - "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz", + "integrity": "sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==", "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-wrap-function": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/types": "^7.12.1" } }, "@babel/helper-replace-supers": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", - "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.5.tgz", + "integrity": "sha512-5YILoed0ZyIpF4gKcpZitEnXEJ9UoDRki1Ey6xz46rxOzfNMAhVIJMoune1hmPVxh40LRv1+oafz7UsWX+vyWA==", "requires": { - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.8.6", - "@babel/types": "^7.8.6" + "@babel/helper-member-expression-to-functions": "^7.12.1", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" } }, "@babel/helper-simple-access": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", - "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", + "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", "requires": { - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/types": "^7.12.1" } }, "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz", - "integrity": "sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q==", - "dev": true, + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", + "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", "requires": { - "@babel/types": "^7.11.0" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true - }, - "@babel/types": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", - "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - } + "@babel/types": "^7.12.1" } }, "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.11.0" } }, "@babel/helper-validator-identifier": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", - "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==" + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" + }, + "@babel/helper-validator-option": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz", + "integrity": "sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A==", + "dev": true }, "@babel/helper-wrap-function": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz", - "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==", + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz", + "integrity": "sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==", "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/helper-function-name": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helpers": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.2.tgz", - "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==", - "dev": true, + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", + "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", "requires": { - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0" + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" } }, "@babel/highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", - "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "requires": { - "@babel/helper-validator-identifier": "^7.9.0", + "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -565,577 +402,164 @@ } }, "@babel/parser": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", - "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==" + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.7.tgz", + "integrity": "sha512-oWR02Ubp4xTLCAqPRiNIuMVgNO5Aif/xpXtabhzW2HWUD47XJsAB4Zd/Rg30+XeQA3juXigV7hlquOTmwqLiwg==" }, "@babel/plugin-external-helpers": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.8.3.tgz", - "integrity": "sha512-mx0WXDDiIl5DwzMtzWGRSPugXi9BxROS05GQrhLNbEamhBiicgn994ibwkyiBH+6png7bm/yA7AUsvHyCXi4Vw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.12.1.tgz", + "integrity": "sha512-5VBqan0daXhDSRjrq2miABuELRwWJWFdM42Jvs/CDuhp+Es+fW+ISA5l+co8d+9oN3WLz/N3VvzyeseL3AvjxA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz", - "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz", + "integrity": "sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.12.1", "@babel/plugin-syntax-async-generators": "^7.8.0" } }, "@babel/plugin-proposal-class-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz", - "integrity": "sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz", + "integrity": "sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-class-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-proposal-decorators": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.10.5.tgz", - "integrity": "sha512-Sc5TAQSZuLzgY0664mMDn24Vw2P8g/VhyLyGPaWiHahhgLqeZvcGeyBZOrJW0oSKIK2mvQ22a1ENXBIQLhrEiQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.12.1.tgz", + "integrity": "sha512-knNIuusychgYN8fGJHONL0RbFxLGawhXOJNLBk75TniTsZZeA+wdkDuv6wp4lGwzQEKjZi6/WYtnb3udNPmQmQ==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.5", + "@babel/helper-create-class-features-plugin": "^7.12.1", "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-decorators": "^7.10.4" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/generator": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", - "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", - "dev": true, - "requires": { - "@babel/types": "^7.11.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", - "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.10.5", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4" - } - }, - "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", - "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", - "dev": true, - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", - "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", - "dev": true - }, - "@babel/helper-replace-supers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", - "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", - "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", - "dev": true, - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", - "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", - "dev": true - }, - "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/traverse": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", - "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.5", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.11.5", - "@babel/types": "^7.11.5", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "@babel/types": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", - "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } + "@babel/plugin-syntax-decorators": "^7.12.1" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz", - "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz", + "integrity": "sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-dynamic-import": "^7.8.0" } }, "@babel/plugin-proposal-export-default-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.8.3.tgz", - "integrity": "sha512-PYtv2S2OdCdp7GSPDg5ndGZFm9DmWFvuLoS5nBxZCgOBggluLnhTScspJxng96alHQzPyrrHxvC9/w4bFuspeA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.12.1.tgz", + "integrity": "sha512-z5Q4Ke7j0AexQRfgUvnD+BdCSgpTEKnqQ3kskk2jWtOBulxICzd1X9BGt7kmWftxZ2W3++OZdt5gtmC8KLxdRQ==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-export-default-from": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-export-default-from": "^7.12.1" } }, "@babel/plugin-proposal-export-namespace-from": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.10.4.tgz", - "integrity": "sha512-aNdf0LY6/3WXkhh0Fdb6Zk9j1NMD8ovj3F6r0+3j837Pn1S1PdNtcwJ5EG9WkVPNHPxyJDaxMaAOVq4eki0qbg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz", + "integrity": "sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", - "dev": true - } } }, "@babel/plugin-proposal-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz", - "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz", + "integrity": "sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.0" } }, "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.11.0.tgz", - "integrity": "sha512-/f8p4z+Auz0Uaf+i8Ekf1iM7wUNLcViFUGiPxKeXvxTSl63B875YPiVdUDdem7hREcI0E0kSpEhS8tF5RphK7Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz", + "integrity": "sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", - "dev": true - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - } } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz", + "integrity": "sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz", - "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz", + "integrity": "sha512-8c+uy0qmnRTeukiGsjLGy6uVs/TFjJchGXUeBqlG4VWYOdJWkhhVPdQ3uHwbmalfJwv2JsV0qffXP4asRfL2SQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.0.tgz", - "integrity": "sha512-UgqBv6bjq4fDb8uku9f+wcm1J7YxJ5nT7WO/jBr0cl0PLKb7t1O6RNR1kZbjgx2LQtsDI9hwoQVmn0yhXeQyow==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", + "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.12.1" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz", + "integrity": "sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz", - "integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.7.tgz", + "integrity": "sha512-4ovylXZ0PWmwoOvhU2vhnzVNnm88/Sm9nx7V8BPgMvAzn5zDou3/Awy0EjglyubVHasJj+XCEkr/r1X3P5elCA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", "@babel/plugin-syntax-optional-chaining": "^7.8.0" } }, "@babel/plugin-proposal-private-methods": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz", - "integrity": "sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz", + "integrity": "sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-create-class-features-plugin": "^7.12.1", "@babel/helper-plugin-utils": "^7.10.4" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/generator": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", - "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", - "dev": true, - "requires": { - "@babel/types": "^7.11.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", - "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.10.5", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4" - } - }, - "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", - "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", - "dev": true, - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", - "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", - "dev": true - }, - "@babel/helper-replace-supers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", - "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", - "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", - "dev": true, - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", - "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", - "dev": true - }, - "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/traverse": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", - "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.5", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.11.5", - "@babel/types": "^7.11.5", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "@babel/types": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", - "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz", - "integrity": "sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz", + "integrity": "sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.8", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-async-generators": { @@ -1157,28 +581,20 @@ } }, "@babel/plugin-syntax-class-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.8.3.tgz", - "integrity": "sha512-UcAyQWg2bAN647Q+O811tG9MrJ38Z10jjhQdKNAL8fsyPzE3cCN/uT+f55cFVY4aGO4jqJAvmqsuY3GQDwAoXg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", + "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-decorators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.10.4.tgz", - "integrity": "sha512-2NaoC6fAk2VMdhY1eerkfHV+lVYC1u8b+jmRJISqANCJlTxYy19HGdIkkQtix2UtkcPuPu+IlDgrVseZnU03bw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.12.1.tgz", + "integrity": "sha512-ir9YW5daRrTYiy9UJ2TzdNIJEZu8KclVzDcfSt4iEmOtwQ4llPtWInNKJyKnVXp1vE4bbVd5S31M/im3mYMO1w==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", - "dev": true - } } }, "@babel/plugin-syntax-dynamic-import": { @@ -1190,11 +606,11 @@ } }, "@babel/plugin-syntax-export-default-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.8.3.tgz", - "integrity": "sha512-a1qnnsr73KLNIQcQlcQ4ZHxqqfBKM6iNQZW2OMTyxNbA2WC7SHWHtGVpFzWtQAuS2pspkWVzdEBXXx8Ik0Za4w==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.12.1.tgz", + "integrity": "sha512-dP5eGg6tHEkhnRD2/vRG/KJKRSg8gtxu2i+P/8/yFPJn/CfPU5G0/7Gks2i3M6IOVAPQekmsLN9LPsmXFFL4Uw==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-export-namespace-from": { @@ -1207,11 +623,11 @@ } }, "@babel/plugin-syntax-flow": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.8.3.tgz", - "integrity": "sha512-innAx3bUbA0KSYj2E2MNFSn9hiCeowOFLxlsuhXzw8hMQnzkDomUr9QCD7E9VF60NmnG1sNTuuv6Qf4f8INYsg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.12.1.tgz", + "integrity": "sha512-1lBLLmtxrwpm4VKmtVFselI/P3pX+G63fAtUUt6b2Nzgao77KNDwyuRt90Mj2/9pKobtt68FdvjfqohZjg/FCA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-json-strings": { @@ -1224,27 +640,20 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz", - "integrity": "sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", + "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" - } } }, "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.8.3.tgz", - "integrity": "sha512-Zpg2Sgc++37kuFl6ppq2Q7Awc6E6AIW671x5PY8E/f7MCIyPPGK/EoeZXvvY3P42exZ3Q4/t3YOzP/HiN79jDg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-nullish-coalescing-operator": { @@ -1256,12 +665,12 @@ } }, "@babel/plugin-syntax-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz", - "integrity": "sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-object-rest-spread": { @@ -1289,69 +698,68 @@ } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz", - "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", + "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-typescript": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.8.3.tgz", - "integrity": "sha512-GO1MQ/SGGGoiEXY0e0bSpHimJvxqB7lktLLIq2pv8xG7WZ8IMEle74jIe1FhprHBWjwjZtXHkycDLZXIWM5Wfg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.1.tgz", + "integrity": "sha512-UZNEcCY+4Dp9yYRCAHrHDU+9ZXLYaY9MgBXSRLkB9WjYFRR6quJBumfVrEkUxrePPBwFcpWfNKXqVRQQtm7mMA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz", - "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz", + "integrity": "sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz", - "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz", + "integrity": "sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==", "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3" + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.12.1" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz", - "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz", + "integrity": "sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz", - "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz", + "integrity": "sha512-zJyAC9sZdE60r1nVQHblcfCj29Dh2Y0DOvlMkcqSo0ckqjiCwNiUezUKw+RjOCwGfpLRwnAeQ2XlLpsnGkvv9w==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "lodash": "^4.17.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-classes": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz", - "integrity": "sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-define-map": "^7.8.3", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-split-export-declaration": "^7.8.3", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz", + "integrity": "sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-define-map": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.10.4", "globals": "^11.1.0" }, "dependencies": { @@ -1363,269 +771,309 @@ } }, "@babel/plugin-transform-computed-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz", - "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz", + "integrity": "sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-destructuring": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz", - "integrity": "sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz", + "integrity": "sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz", - "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz", + "integrity": "sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz", - "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz", + "integrity": "sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz", - "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz", + "integrity": "sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==", "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-flow-strip-types": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.9.0.tgz", - "integrity": "sha512-7Qfg0lKQhEHs93FChxVLAvhBshOPQDtJUTVHr/ZwQNRccCm4O9D79r9tVSoV8iNwjP1YgfD+e/fgHcPkN1qEQg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.12.1.tgz", + "integrity": "sha512-8hAtkmsQb36yMmEtk2JZ9JnVyDSnDOdlB+0nEGzIDLuK4yR3JcEjfuFPYkdEPSh8Id+rAMeBEn+X0iVEyho6Hg==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-flow": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-flow": "^7.12.1" } }, "@babel/plugin-transform-for-of": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz", - "integrity": "sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz", + "integrity": "sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz", - "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz", + "integrity": "sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==", "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz", - "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz", + "integrity": "sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz", - "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz", + "integrity": "sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz", - "integrity": "sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz", + "integrity": "sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "dependencies": { + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + } } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz", - "integrity": "sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz", + "integrity": "sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==", "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-simple-access": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" - } - }, + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-simple-access": "^7.12.1", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "dependencies": { + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "requires": { + "object.assign": "^4.1.0" + } + } + } + }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz", - "integrity": "sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz", + "integrity": "sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.8.3", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" + "@babel/helper-hoist-variables": "^7.10.4", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-validator-identifier": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "dependencies": { + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + } } }, "@babel/plugin-transform-modules-umd": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz", - "integrity": "sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz", + "integrity": "sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", - "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz", + "integrity": "sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.12.1" } }, "@babel/plugin-transform-new-target": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz", - "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz", + "integrity": "sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-object-assign": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.8.3.tgz", - "integrity": "sha512-i3LuN8tPDqUCRFu3dkzF2r1Nx0jp4scxtm7JxtIqI9he9Vk20YD+/zshdzR9JLsoBMlJlNR82a62vQExNEVx/Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.12.1.tgz", + "integrity": "sha512-geUHn4XwHznRAFiuROTy0Hr7bKbpijJCmr1Svt/VNGhpxmp0OrdxURNpWbOAf94nUbL+xj6gbxRVPHWIbRpRoA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-object-super": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz", - "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz", + "integrity": "sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1" } }, "@babel/plugin-transform-parameters": { - "version": "7.9.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.3.tgz", - "integrity": "sha512-fzrQFQhp7mIhOzmOtPiKffvCYQSK10NR8t6BBz2yPbeUHb9OLW8RZGtgDRBn8z2hGcwvKDL3vC7ojPTLNxmqEg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz", + "integrity": "sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==", "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-property-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz", - "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz", + "integrity": "sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-react-constant-elements": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.9.0.tgz", - "integrity": "sha512-wXMXsToAUOxJuBBEHajqKLFWcCkOSLshTI2ChCFFj1zDd7od4IOxiwLCOObNUvOpkxLpjIuaIdBMmNt6ocCPAw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.12.1.tgz", + "integrity": "sha512-KOHd0tIRLoER+J+8f9DblZDa1fLGPwaaN1DI1TVHuQFOpjHV22C3CUB3obeC4fexHY9nx+fH0hQNvLFFfA1mxA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-react-display-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.8.3.tgz", - "integrity": "sha512-3Jy/PCw8Fe6uBKtEgz3M82ljt+lTg+xJaM4og+eyu83qLT87ZUSckn0wy7r31jflURWLO83TW6Ylf7lyXj3m5A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.1.tgz", + "integrity": "sha512-cAzB+UzBIrekfYxyLlFqf/OagTvHLcVBb5vpouzkYkBclRPraiygVnafvAoipErZLI8ANv8Ecn6E/m5qPXD26w==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-react-jsx": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.9.4.tgz", - "integrity": "sha512-Mjqf3pZBNLt854CK0C/kRuXAnE6H/bo7xYojP+WGtX8glDGSibcwnsWwhwoSuRg0+EBnxPC1ouVnuetUIlPSAw==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.7.tgz", + "integrity": "sha512-YFlTi6MEsclFAPIDNZYiCRbneg1MFGao9pPG9uD5htwE0vDbPaMUMeYd6itWjw7K4kro4UbdQf3ljmFl9y48dQ==", "requires": { - "@babel/helper-builder-react-jsx": "^7.9.0", - "@babel/helper-builder-react-jsx-experimental": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" + "@babel/helper-builder-react-jsx": "^7.10.4", + "@babel/helper-builder-react-jsx-experimental": "^7.12.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.12.1" } }, "@babel/plugin-transform-react-jsx-development": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.9.0.tgz", - "integrity": "sha512-tK8hWKrQncVvrhvtOiPpKrQjfNX3DtkNLSX4ObuGcpS9p0QrGetKmlySIGR07y48Zft8WVgPakqd/bk46JrMSw==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.7.tgz", + "integrity": "sha512-Rs3ETtMtR3VLXFeYRChle5SsP/P9Jp/6dsewBQfokDSzKJThlsuFcnzLTDRALiUmTC48ej19YD9uN1mupEeEDg==", "dev": true, "requires": { - "@babel/helper-builder-react-jsx-experimental": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" + "@babel/helper-builder-react-jsx-experimental": "^7.12.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.12.1" } }, "@babel/plugin-transform-react-jsx-self": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.9.0.tgz", - "integrity": "sha512-K2ObbWPKT7KUTAoyjCsFilOkEgMvFG+y0FqOl6Lezd0/13kMkkjHskVsZvblRPj1PHA44PrToaZANrryppzTvQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.12.1.tgz", + "integrity": "sha512-FbpL0ieNWiiBB5tCldX17EtXgmzeEZjFrix72rQYeq9X6nUK38HCaxexzVQrZWXanxKJPKVVIU37gFjEQYkPkA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-react-jsx-source": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.9.0.tgz", - "integrity": "sha512-K6m3LlSnTSfRkM6FcRk8saNEeaeyG5k7AVkBU2bZK3+1zdkSED3qNdsWrUgQBeTVD2Tp3VMmerxVO2yM5iITmw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.12.1.tgz", + "integrity": "sha512-keQ5kBfjJNRc6zZN1/nVHCd6LLIHq4aUKcVnvE/2l+ZZROSbqoiGFRtT5t3Is89XJxBQaP7NLZX2jgGHdZvvFQ==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-react-pure-annotations": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz", + "integrity": "sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-regenerator": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz", - "integrity": "sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz", + "integrity": "sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==", "requires": { "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz", - "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz", + "integrity": "sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-runtime": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.0.tgz", - "integrity": "sha512-pUu9VSf3kI1OqbWINQ7MaugnitRss1z533436waNXp+0N3ur3zfut37sXiQMxkuCF4VUjwZucen/quskCh7NHw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.1.tgz", + "integrity": "sha512-Ac/H6G9FEIkS2tXsZjL4RAdS3L3WHxci0usAnz7laPWUmFiGtj7tIASChqKZMHTSQTQY6xDbOq+V1/vIq3QrWg==", "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", "resolve": "^1.8.1", "semver": "^5.5.1" }, @@ -1638,155 +1086,166 @@ } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz", - "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", + "integrity": "sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz", - "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz", + "integrity": "sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz", - "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz", + "integrity": "sha512-VEiqZL5N/QvDbdjfYQBhruN0HYjSPjC4XkeqW4ny/jNtH9gcbgaqBIXYEZCNnESMAGs0/K/R7oFGMhOyu/eIxg==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-regex": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-template-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz", - "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz", + "integrity": "sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==", "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz", - "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz", + "integrity": "sha512-EPGgpGy+O5Kg5pJFNDKuxt9RdmTgj5sgrus2XVeMp/ZIbOESadgILUbm50SNpghOh3/6yrbsH+NB5+WJTmsA7Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-typescript": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.9.4.tgz", - "integrity": "sha512-yeWeUkKx2auDbSxRe8MusAG+n4m9BFY/v+lPjmQDgOFX5qnySkUY5oXzkp6FwPdsYqnKay6lorXYdC0n3bZO7w==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.12.1.tgz", + "integrity": "sha512-VrsBByqAIntM+EYMqSm59SiMEf7qkmI9dqMt6RbD/wlwueWmYcI0FFK5Fj47pP6DRZm+3teXjosKlwcZJ5lIMw==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-typescript": "^7.8.3" + "@babel/helper-create-class-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-typescript": "^7.12.1" } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz", - "integrity": "sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz", + "integrity": "sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", - "dev": true - } } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz", - "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz", + "integrity": "sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/preset-env": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.0.tgz", - "integrity": "sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.9.0", - "@babel/helper-compilation-targets": "^7.8.7", - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-proposal-async-generator-functions": "^7.8.3", - "@babel/plugin-proposal-dynamic-import": "^7.8.3", - "@babel/plugin-proposal-json-strings": "^7.8.3", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-proposal-numeric-separator": "^7.8.3", - "@babel/plugin-proposal-object-rest-spread": "^7.9.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", - "@babel/plugin-proposal-optional-chaining": "^7.9.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.7.tgz", + "integrity": "sha512-OnNdfAr1FUQg7ksb7bmbKoby4qFOHw6DKWWUNB9KqnnCldxhxJlP+21dpyaWFmf2h0rTbOkXJtAGevY3XW1eew==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.12.7", + "@babel/helper-compilation-targets": "^7.12.5", + "@babel/helper-module-imports": "^7.12.5", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-validator-option": "^7.12.1", + "@babel/plugin-proposal-async-generator-functions": "^7.12.1", + "@babel/plugin-proposal-class-properties": "^7.12.1", + "@babel/plugin-proposal-dynamic-import": "^7.12.1", + "@babel/plugin-proposal-export-namespace-from": "^7.12.1", + "@babel/plugin-proposal-json-strings": "^7.12.1", + "@babel/plugin-proposal-logical-assignment-operators": "^7.12.1", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", + "@babel/plugin-proposal-numeric-separator": "^7.12.7", + "@babel/plugin-proposal-object-rest-spread": "^7.12.1", + "@babel/plugin-proposal-optional-catch-binding": "^7.12.1", + "@babel/plugin-proposal-optional-chaining": "^7.12.7", + "@babel/plugin-proposal-private-methods": "^7.12.1", + "@babel/plugin-proposal-unicode-property-regex": "^7.12.1", "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-class-properties": "^7.12.1", "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.8.3", - "@babel/plugin-transform-arrow-functions": "^7.8.3", - "@babel/plugin-transform-async-to-generator": "^7.8.3", - "@babel/plugin-transform-block-scoped-functions": "^7.8.3", - "@babel/plugin-transform-block-scoping": "^7.8.3", - "@babel/plugin-transform-classes": "^7.9.0", - "@babel/plugin-transform-computed-properties": "^7.8.3", - "@babel/plugin-transform-destructuring": "^7.8.3", - "@babel/plugin-transform-dotall-regex": "^7.8.3", - "@babel/plugin-transform-duplicate-keys": "^7.8.3", - "@babel/plugin-transform-exponentiation-operator": "^7.8.3", - "@babel/plugin-transform-for-of": "^7.9.0", - "@babel/plugin-transform-function-name": "^7.8.3", - "@babel/plugin-transform-literals": "^7.8.3", - "@babel/plugin-transform-member-expression-literals": "^7.8.3", - "@babel/plugin-transform-modules-amd": "^7.9.0", - "@babel/plugin-transform-modules-commonjs": "^7.9.0", - "@babel/plugin-transform-modules-systemjs": "^7.9.0", - "@babel/plugin-transform-modules-umd": "^7.9.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", - "@babel/plugin-transform-new-target": "^7.8.3", - "@babel/plugin-transform-object-super": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.8.7", - "@babel/plugin-transform-property-literals": "^7.8.3", - "@babel/plugin-transform-regenerator": "^7.8.7", - "@babel/plugin-transform-reserved-words": "^7.8.3", - "@babel/plugin-transform-shorthand-properties": "^7.8.3", - "@babel/plugin-transform-spread": "^7.8.3", - "@babel/plugin-transform-sticky-regex": "^7.8.3", - "@babel/plugin-transform-template-literals": "^7.8.3", - "@babel/plugin-transform-typeof-symbol": "^7.8.4", - "@babel/plugin-transform-unicode-regex": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.12.1", + "@babel/plugin-transform-arrow-functions": "^7.12.1", + "@babel/plugin-transform-async-to-generator": "^7.12.1", + "@babel/plugin-transform-block-scoped-functions": "^7.12.1", + "@babel/plugin-transform-block-scoping": "^7.12.1", + "@babel/plugin-transform-classes": "^7.12.1", + "@babel/plugin-transform-computed-properties": "^7.12.1", + "@babel/plugin-transform-destructuring": "^7.12.1", + "@babel/plugin-transform-dotall-regex": "^7.12.1", + "@babel/plugin-transform-duplicate-keys": "^7.12.1", + "@babel/plugin-transform-exponentiation-operator": "^7.12.1", + "@babel/plugin-transform-for-of": "^7.12.1", + "@babel/plugin-transform-function-name": "^7.12.1", + "@babel/plugin-transform-literals": "^7.12.1", + "@babel/plugin-transform-member-expression-literals": "^7.12.1", + "@babel/plugin-transform-modules-amd": "^7.12.1", + "@babel/plugin-transform-modules-commonjs": "^7.12.1", + "@babel/plugin-transform-modules-systemjs": "^7.12.1", + "@babel/plugin-transform-modules-umd": "^7.12.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.1", + "@babel/plugin-transform-new-target": "^7.12.1", + "@babel/plugin-transform-object-super": "^7.12.1", + "@babel/plugin-transform-parameters": "^7.12.1", + "@babel/plugin-transform-property-literals": "^7.12.1", + "@babel/plugin-transform-regenerator": "^7.12.1", + "@babel/plugin-transform-reserved-words": "^7.12.1", + "@babel/plugin-transform-shorthand-properties": "^7.12.1", + "@babel/plugin-transform-spread": "^7.12.1", + "@babel/plugin-transform-sticky-regex": "^7.12.7", + "@babel/plugin-transform-template-literals": "^7.12.1", + "@babel/plugin-transform-typeof-symbol": "^7.12.1", + "@babel/plugin-transform-unicode-escapes": "^7.12.1", + "@babel/plugin-transform-unicode-regex": "^7.12.1", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.9.0", - "browserslist": "^4.9.1", - "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", + "@babel/types": "^7.12.7", + "core-js-compat": "^3.7.0", "semver": "^5.5.0" }, "dependencies": { - "leven": { - "version": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" + "core-js-compat": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.1.tgz", + "integrity": "sha512-a16TLmy9NVD1rkjUGbwuyWkiDoN0FDpAwrfLONvHFQx0D9k7J9y0srwMT8QP/Z6HE3MIFaVynEeYwZwPX1o5RQ==", + "dev": true, + "requires": { + "browserslist": "^4.15.0", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } }, "semver": { "version": "5.7.1", @@ -1797,19 +1256,19 @@ } }, "@babel/preset-flow": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.9.0.tgz", - "integrity": "sha512-88uSmlshIrlmPkNkEcx3UpSZ6b8n0UGBq0/0ZMZCF/uxAW0XIAUuDHBhIOAh0pvweafH4RxOwi/H3rWhtqOYPA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.12.1.tgz", + "integrity": "sha512-UAoyMdioAhM6H99qPoKvpHMzxmNVXno8GYU/7vZmGaHk6/KqfDYL1W0NxszVbJ2EP271b7e6Ox+Vk2A9QsB3Sw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-transform-flow-strip-types": "^7.9.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-transform-flow-strip-types": "^7.12.1" } }, "@babel/preset-modules": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", - "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1820,36 +1279,38 @@ } }, "@babel/preset-react": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.9.4.tgz", - "integrity": "sha512-AxylVB3FXeOTQXNXyiuAQJSvss62FEotbX2Pzx3K/7c+MKJMdSg6Ose6QYllkdCFA8EInCJVw7M/o5QbLuA4ZQ==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.12.7.tgz", + "integrity": "sha512-wKeTdnGUP5AEYCYQIMeXMMwU7j+2opxrG0WzuZfxuuW9nhKvvALBjl67653CWamZJVefuJGI219G591RSldrqQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-transform-react-display-name": "^7.8.3", - "@babel/plugin-transform-react-jsx": "^7.9.4", - "@babel/plugin-transform-react-jsx-development": "^7.9.0", - "@babel/plugin-transform-react-jsx-self": "^7.9.0", - "@babel/plugin-transform-react-jsx-source": "^7.9.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-transform-react-display-name": "^7.12.1", + "@babel/plugin-transform-react-jsx": "^7.12.7", + "@babel/plugin-transform-react-jsx-development": "^7.12.7", + "@babel/plugin-transform-react-jsx-self": "^7.12.1", + "@babel/plugin-transform-react-jsx-source": "^7.12.1", + "@babel/plugin-transform-react-pure-annotations": "^7.12.1" } }, "@babel/preset-typescript": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.9.0.tgz", - "integrity": "sha512-S4cueFnGrIbvYJgwsVFKdvOmpiL0XGw9MFW9D0vgRys5g36PBhZRL8NX8Gr2akz8XRtzq6HuDXPD/1nniagNUg==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.12.7.tgz", + "integrity": "sha512-nOoIqIqBmHBSEgBXWR4Dv/XBehtIFcw9PqZw6rFYuKrzsZmOQm3PR5siLBnKZFEsDb03IegG8nSjU/iXXXYRmw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-transform-typescript": "^7.9.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-validator-option": "^7.12.1", + "@babel/plugin-transform-typescript": "^7.12.1" } }, "@babel/register": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.9.0.tgz", - "integrity": "sha512-Tv8Zyi2J2VRR8g7pC5gTeIN8Ihultbmk0ocyNz8H2nEZbmhp1N6q0A1UGsQbDvGP/sNinQKUHf3SqXwqjtFv4Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.12.1.tgz", + "integrity": "sha512-XWcmseMIncOjoydKZnWvWi0/5CUCD+ZYKhRwgYlWOrA8fGZ/FjuLRpqtIhLOVD/fvR1b9DQHtZPn68VvhpYf+Q==", "requires": { "find-cache-dir": "^2.0.0", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "make-dir": "^2.1.0", "pirates": "^4.0.0", "source-map-support": "^0.5.16" @@ -1929,32 +1390,28 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "source-map": { - "version": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, "@babel/runtime": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", - "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", + "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", "requires": { "regenerator-runtime": "^0.13.4" }, "dependencies": { "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" } } }, "@babel/runtime-corejs3": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz", - "integrity": "sha512-qh5IR+8VgFz83VBa6OkaET6uN/mJOhHONuy3m1sgF0CV6mXdPSEBdA7e1eUbVvyNtANjMbg22JUv71BaDXLY6A==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.5.tgz", + "integrity": "sha512-roGr54CsTmNPPzZoCP1AmDXuBoNao7tnSA83TXTwt+UK5QVyh1DIJnrgYRPWKCF2flqZQXwa7Yr8v7VmLzF0YQ==", "dev": true, "requires": { "core-js-pure": "^3.0.0", @@ -1970,131 +1427,37 @@ } }, "@babel/template": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", - "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6" + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" } }, "@babel/traverse": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", - "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "version": "7.12.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.9.tgz", + "integrity": "sha512-iX9ajqnLdoU1s1nHt36JDI9KG4k+vmI8WgjK5d+aDTwQbL2fUnzedNedssA645Ede3PM2ma1n8Q4h2ohwXgMXw==", "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.5", + "@babel/generator": "^7.12.5", "@babel/helper-function-name": "^7.10.4", "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.11.5", - "@babel/types": "^7.11.5", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.19" }, "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/generator": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", - "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", - "requires": { - "@babel/types": "^7.11.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", - "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", - "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", - "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==" - }, - "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/types": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", - "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "globals": { @@ -2110,12 +1473,12 @@ } }, "@babel/types": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", - "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.7.tgz", + "integrity": "sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ==", "requires": { - "@babel/helper-validator-identifier": "^7.9.0", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } }, @@ -3088,12 +2451,11 @@ "dev": true }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -3180,9 +2542,9 @@ } }, "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.2.1.tgz", + "integrity": "sha512-bTLYHSeC0UH/EFXS9KqWnXuOl/wHK5Z/d+ghd5AsFMYN7wIGkUCOJyzy88+wJKkZPGON8u4Z9f6U4FdgURE9qA==", "dev": true, "optional": true }, @@ -3326,12 +2688,6 @@ "supports-color": "^7.0.0" } }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -3433,14 +2789,14 @@ "dev": true }, "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -3512,11 +2868,12 @@ "dev": true }, "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", "dev": true, "requires": { + "is-core-module": "^2.1.0", "path-parse": "^1.0.6" } }, @@ -3548,9 +2905,9 @@ } }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -3659,12 +3016,11 @@ } }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -3784,9 +3140,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -3844,30 +3200,6 @@ "expect": "^25.5.0" }, "dependencies": { - "@jest/environment": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-25.5.0.tgz", - "integrity": "sha512-U2VXPEqL07E/V7pSZMSQCvV5Ea4lqOlT+0ZFijl/i316cRMHvZ4qC+jBdryd+lmRetjQo0YIQr6cVPNxxK87mA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^25.5.0", - "@jest/types": "^25.5.0", - "jest-mock": "^25.5.0" - } - }, - "@jest/fake-timers": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-25.5.0.tgz", - "integrity": "sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ==", - "dev": true, - "requires": { - "@jest/types": "^25.5.0", - "jest-message-util": "^25.5.0", - "jest-mock": "^25.5.0", - "jest-util": "^25.5.0", - "lolex": "^5.0.0" - } - }, "@jest/types": { "version": "25.5.0", "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", @@ -3880,31 +3212,15 @@ "chalk": "^3.0.0" } }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, "chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -3930,172 +3246,20 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "expect": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-25.5.0.tgz", - "integrity": "sha512-w7KAXo0+6qqZZhovCaBVPSIqQp7/UTcx4M9uKt2m6pd2VB1voyC8JizLRqeEqud3AAVP02g+hbErDu5gu64tlA==", - "dev": true, - "requires": { - "@jest/types": "^25.5.0", - "ansi-styles": "^4.0.0", - "jest-get-type": "^25.2.6", - "jest-matcher-utils": "^25.5.0", - "jest-message-util": "^25.5.0", - "jest-regex-util": "^25.2.6" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "jest-diff": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz", - "integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==", - "dev": true, - "requires": { - "chalk": "^3.0.0", - "diff-sequences": "^25.2.6", - "jest-get-type": "^25.2.6", - "pretty-format": "^25.5.0" - } - }, - "jest-get-type": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", - "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", - "dev": true - }, - "jest-matcher-utils": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz", - "integrity": "sha512-VWI269+9JS5cpndnpCwm7dy7JtGQT30UHfrnM3mXl22gHGt/b7NkjBqXfbhZ8V4B7ANUsjK18PlSBmG0YH7gjw==", - "dev": true, - "requires": { - "chalk": "^3.0.0", - "jest-diff": "^25.5.0", - "jest-get-type": "^25.2.6", - "pretty-format": "^25.5.0" - } - }, - "jest-message-util": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-25.5.0.tgz", - "integrity": "sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^25.5.0", - "@types/stack-utils": "^1.0.1", - "chalk": "^3.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "slash": "^3.0.0", - "stack-utils": "^1.0.1" - } - }, - "jest-mock": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-25.5.0.tgz", - "integrity": "sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA==", - "dev": true, - "requires": { - "@jest/types": "^25.5.0" - } - }, - "jest-regex-util": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-25.2.6.tgz", - "integrity": "sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw==", - "dev": true - }, - "jest-util": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-25.5.0.tgz", - "integrity": "sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA==", - "dev": true, - "requires": { - "@jest/types": "^25.5.0", - "chalk": "^3.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "make-dir": "^3.0.0" - } - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "pretty-format": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", - "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", - "dev": true, - "requires": { - "@jest/types": "^25.5.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^16.12.0" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } } } }, @@ -4267,9 +3431,9 @@ } }, "fsevents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.2.0.tgz", + "integrity": "sha512-pKnaUh2TNvk+/egJdBw1h46LwyLx8BzEq+MGCf/RMCVfEHHsGOCWG00dqk91kUPPArIIwMBg9T/virxwzP03cA==", "dev": true, "optional": true }, @@ -4581,12 +3745,11 @@ } }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -4644,9 +3807,9 @@ } }, "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.2.1.tgz", + "integrity": "sha512-bTLYHSeC0UH/EFXS9KqWnXuOl/wHK5Z/d+ghd5AsFMYN7wIGkUCOJyzy88+wJKkZPGON8u4Z9f6U4FdgURE9qA==", "dev": true, "optional": true }, @@ -4766,9 +3929,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -6249,6 +5412,15 @@ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, + "query-string": { + "version": "6.13.6", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.13.6.tgz", + "integrity": "sha512-/WWZ7d9na6s2wMEGdVCVgKWE9Rt7nYyNIf7k8xmHXcesPMlEzicWo3lbYwHyA4wBktI2KrXxxZeACLbE84hvSQ==", + "requires": { + "decode-uri-component": "^0.2.0", + "split-on-first": "^1.0.0" + } + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -7900,9 +7072,9 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, "query-string": { - "version": "6.13.6", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.13.6.tgz", - "integrity": "sha512-/WWZ7d9na6s2wMEGdVCVgKWE9Rt7nYyNIf7k8xmHXcesPMlEzicWo3lbYwHyA4wBktI2KrXxxZeACLbE84hvSQ==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.13.1.tgz", + "integrity": "sha512-RfoButmcK+yCta1+FuU8REvisx1oEzhMKwhLUNcepQTPGcNMp1sIqjnfCtfnvGSQZQEhaBHvccujtWoUV3TTbA==", "requires": { "decode-uri-component": "^0.2.0", "split-on-first": "^1.0.0", @@ -7963,9 +7135,9 @@ "dev": true }, "@sinonjs/commons": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.2.tgz", - "integrity": "sha512-+DUO6pnp3udV/v2VfUWgaY5BIE1IfT7lLfeDzPVeMT1XKkaAp9LgSI9x5RtrFQoZ9Oi0PgXQQHPaoKu7dCjVxw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", + "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", "dev": true, "requires": { "type-detect": "4.0.8" @@ -8471,1365 +7643,2090 @@ "webpack-virtual-modules": "^0.2.2" }, "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", "dev": true, "requires": { - "@babel/highlight": "^7.10.4" + "@emotion/memoize": "0.7.4" } }, - "@babel/compat-data": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz", - "integrity": "sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==", + "@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "dev": true + }, + "@reach/router": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@reach/router/-/router-1.3.4.tgz", + "integrity": "sha512-+mtn9wjlB9NN2CNnnC/BRYtwdKBfSyyasPYraNAyvaV1occr/5NnB4CVzjEZipNHwYebQwcndGUmpFzxAUoqSA==", "dev": true, "requires": { - "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "semver": "^5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "create-react-context": "0.3.0", + "invariant": "^2.2.3", + "prop-types": "^15.6.1", + "react-lifecycles-compat": "^3.0.4" } }, - "@babel/generator": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", - "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "@storybook/addons": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.0.21.tgz", + "integrity": "sha512-yDttNLc3vXqBxwK795ykgzTC6MpvuXDQuF4LHSlHZQe6wsMu1m3fljnbYdafJWdx6cNZwUblU3KYcR11PqhkPg==", "dev": true, "requires": { - "@babel/types": "^7.11.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@storybook/api": "6.0.21", + "@storybook/channels": "6.0.21", + "@storybook/client-logger": "6.0.21", + "@storybook/core-events": "6.0.21", + "@storybook/router": "6.0.21", + "@storybook/theming": "6.0.21", + "core-js": "^3.0.1", + "global": "^4.3.2", + "regenerator-runtime": "^0.13.3" } }, - "@babel/helper-annotate-as-pure": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", - "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", + "@storybook/api": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/api/-/api-6.0.21.tgz", + "integrity": "sha512-cRRGf/KGFwYiDouTouEcDdp45N1AbYnAfvLqYZ3KuUTGZ+CiU/PN/vavkp07DQeM4FIQO8TLhzHdsLFpLT7Lkw==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@reach/router": "^1.3.3", + "@storybook/channels": "6.0.21", + "@storybook/client-logger": "6.0.21", + "@storybook/core-events": "6.0.21", + "@storybook/csf": "0.0.1", + "@storybook/router": "6.0.21", + "@storybook/semver": "^7.3.2", + "@storybook/theming": "6.0.21", + "@types/reach__router": "^1.3.5", + "core-js": "^3.0.1", + "fast-deep-equal": "^3.1.1", + "global": "^4.3.2", + "lodash": "^4.17.15", + "memoizerific": "^1.11.3", + "react": "^16.8.3", + "regenerator-runtime": "^0.13.3", + "store2": "^2.7.1", + "telejson": "^5.0.2", + "ts-dedent": "^1.1.1", + "util-deprecate": "^1.0.2" } }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", - "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", + "@storybook/channel-postmessage": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.0.21.tgz", + "integrity": "sha512-ArRnoaS+b7qpAku/SO27z/yjRDCXb37mCPYGX0ntPbiQajootUbGO7otfnjFkaP44hCEC9uDYlOfMU1hYU1N6A==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.10.4", - "@babel/types": "^7.10.4" + "@storybook/channels": "6.0.21", + "@storybook/client-logger": "6.0.21", + "@storybook/core-events": "6.0.21", + "core-js": "^3.0.1", + "global": "^4.3.2", + "qs": "^6.6.0", + "telejson": "^5.0.2" } }, - "@babel/helper-compilation-targets": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz", - "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", + "@storybook/channels": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.0.21.tgz", + "integrity": "sha512-G6gjcEotSwDmOlxSmOMgsO3VhQ42RLJK7kFp6D5eg0Q6S8vsypltdT8orxdu+6+AbcBrL+5Sla8lThzaCvXsVQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.10.4", - "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "levenary": "^1.1.1", - "semver": "^5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "core-js": "^3.0.1", + "ts-dedent": "^1.1.1", + "util-deprecate": "^1.0.2" } }, - "@babel/helper-create-class-features-plugin": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", - "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", + "@storybook/client-api": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.0.21.tgz", + "integrity": "sha512-emBXd/ml6pc3G8gP3MsR9zQsAq1zZbqof9MxB51tG/jpTXdqWQ8ce1pt1tJS8Xj0QDM072jR6wsY+mmro0GZnA==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.10.5", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4" + "@storybook/addons": "6.0.21", + "@storybook/channel-postmessage": "6.0.21", + "@storybook/channels": "6.0.21", + "@storybook/client-logger": "6.0.21", + "@storybook/core-events": "6.0.21", + "@storybook/csf": "0.0.1", + "@types/qs": "^6.9.0", + "@types/webpack-env": "^1.15.2", + "core-js": "^3.0.1", + "global": "^4.3.2", + "lodash": "^4.17.15", + "memoizerific": "^1.11.3", + "qs": "^6.6.0", + "stable": "^0.1.8", + "store2": "^2.7.1", + "ts-dedent": "^1.1.1", + "util-deprecate": "^1.0.2" } }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz", - "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==", + "@storybook/client-logger": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.0.21.tgz", + "integrity": "sha512-8aUEbhjXV+UMYQWukVYnp+kZafF+LD4Dm7eMo37IUZvt3VIjV1VvhxIDVJtqjk2vv0KZTepESFBkZQLmBzI9Zg==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-regex": "^7.10.4", - "regexpu-core": "^4.7.0" + "core-js": "^3.0.1", + "global": "^4.3.2" } }, - "@babel/helper-define-map": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", - "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", + "@storybook/components": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-6.0.21.tgz", + "integrity": "sha512-r6btqFW/rcXIU5v231EifZfdh9O0fy7bJDXwwDf8zVUgLx8JRc0VnSs3nvK3Is9HF1wZ9vjx/7Lh4rTIDZAjgg==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/types": "^7.10.5", - "lodash": "^4.17.19" + "@storybook/client-logger": "6.0.21", + "@storybook/csf": "0.0.1", + "@storybook/theming": "6.0.21", + "@types/overlayscrollbars": "^1.9.0", + "@types/react-color": "^3.0.1", + "@types/react-syntax-highlighter": "11.0.4", + "core-js": "^3.0.1", + "fast-deep-equal": "^3.1.1", + "global": "^4.3.2", + "lodash": "^4.17.15", + "markdown-to-jsx": "^6.11.4", + "memoizerific": "^1.11.3", + "overlayscrollbars": "^1.10.2", + "polished": "^3.4.4", + "popper.js": "^1.14.7", + "react": "^16.8.3", + "react-color": "^2.17.0", + "react-dom": "^16.8.3", + "react-popper-tooltip": "^2.11.0", + "react-syntax-highlighter": "^12.2.1", + "react-textarea-autosize": "^8.1.1", + "ts-dedent": "^1.1.1" } }, - "@babel/helper-explode-assignable-expression": { - "version": "7.11.4", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.11.4.tgz", - "integrity": "sha512-ux9hm3zR4WV1Y3xXxXkdG/0gxF9nvI0YVmKVhvK9AfMoaQkemL3sJpXw+Xbz65azo8qJiEz2XVDUpK3KYhH3ZQ==", + "@storybook/core-events": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.0.21.tgz", + "integrity": "sha512-p84fbPcsAhnqDhp+HJ4P8+vI2BqJus4IRoVAemLAwuPjyPElrV9UvOa/RHy1BN8Z6jXwFA+FFzfGl2kPJ3WYcA==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "core-js": "^3.0.1" } }, - "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "@storybook/router": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.0.21.tgz", + "integrity": "sha512-46SsKJfcd12lRrISnfrWhicJx8EylkgGDGohfH0n5p7inkkGOkKV8QFZoYPRKZueMXmUKpzJ0Z3HmVsLTCrCDw==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" + "@reach/router": "^1.3.3", + "@types/reach__router": "^1.3.5", + "core-js": "^3.0.1", + "global": "^4.3.2", + "memoizerific": "^1.11.3", + "qs": "^6.6.0" } }, - "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "@storybook/semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "core-js": "^3.6.5", + "find-up": "^4.1.0" + }, + "dependencies": { + "core-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", + "dev": true + } } }, - "@babel/helper-hoist-variables": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", - "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", + "@storybook/theming": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.0.21.tgz", + "integrity": "sha512-n97DfB9kG6WrV1xBGDyeQibTrh8pBBCp3dSL3UTGH+KX3C2+4sm6QHlTgyekbi5FrbFEbnuZOKAS3YbLVONsRQ==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@emotion/core": "^10.0.20", + "@emotion/is-prop-valid": "^0.8.6", + "@emotion/styled": "^10.0.17", + "@storybook/client-logger": "6.0.21", + "core-js": "^3.0.1", + "deep-object-diff": "^1.1.0", + "emotion-theming": "^10.0.19", + "global": "^4.3.2", + "memoizerific": "^1.11.3", + "polished": "^3.4.4", + "resolve-from": "^5.0.0", + "ts-dedent": "^1.1.1" } }, - "@babel/helper-member-expression-to-functions": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", - "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "@types/json-schema": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "dev": true + }, + "@types/reach__router": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/reach__router/-/reach__router-1.3.5.tgz", + "integrity": "sha512-h0NbqXN/tJuBY/xggZSej1SKQEstbHO7J/omt1tYoFGmj3YXOodZKbbqD4mNDh7zvEGYd7YFrac1LTtAr3xsYQ==", "dev": true, "requires": { - "@babel/types": "^7.11.0" + "@types/history": "*", + "@types/react": "*" } }, - "@babel/helper-module-imports": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", - "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "@types/react-syntax-highlighter": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.4.tgz", + "integrity": "sha512-9GfTo3a0PHwQeTVoqs0g5bS28KkSY48pp5659wA+Dp4MqceDEa8EHBqrllJvvtyusszyJhViUEap0FDvlk/9Zg==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@types/react": "*" } }, - "@babel/helper-module-transforms": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", - "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-simple-access": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/template": "^7.10.4", - "@babel/types": "^7.11.0", - "lodash": "^4.17.19" + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" } }, - "@babel/helper-optimise-call-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", - "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", + "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", + "dev": true + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", + "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@webassemblyjs/wast-printer": "1.9.0" } }, - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "@webassemblyjs/helper-fsm": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", + "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", "dev": true }, - "@babel/helper-regex": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", - "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", + "@webassemblyjs/helper-module-context": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", + "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", "dev": true, "requires": { - "lodash": "^4.17.19" + "@webassemblyjs/ast": "1.9.0" } }, - "@babel/helper-remap-async-to-generator": { - "version": "7.11.4", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz", - "integrity": "sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA==", + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-wrap-function": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" } }, - "@babel/helper-replace-supers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", - "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "@webassemblyjs/ieee754": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "@xtuc/ieee754": "^1.2.0" } }, - "@babel/helper-simple-access": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", - "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "@webassemblyjs/leb128": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", "dev": true, "requires": { - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" + "@xtuc/long": "4.2.2" } }, - "@babel/helper-split-export-declaration": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", - "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "@webassemblyjs/utf8": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", "dev": true, "requires": { - "@babel/types": "^7.11.0" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" } }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true - }, - "@babel/helper-wrap-function": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz", - "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==", + "@webassemblyjs/wasm-gen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" } }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "@webassemblyjs/wasm-opt": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" } }, - "@babel/parser": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", - "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", - "dev": true - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", - "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", + "@webassemblyjs/wasm-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.10.4", - "@babel/plugin-syntax-async-generators": "^7.8.0" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" } }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz", - "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==", + "@webassemblyjs/wast-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", + "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/floating-point-hex-parser": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-code-frame": "1.9.0", + "@webassemblyjs/helper-fsm": "1.9.0", + "@xtuc/long": "4.2.2" } }, - "@babel/plugin-proposal-json-strings": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", - "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==", + "@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.0" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" } }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", - "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", + "ajv": { + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", + "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz", - "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==", + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", "dev": true, + "optional": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" } }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz", - "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==", + "autoprefixer": { + "version": "9.8.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", + "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.10.4" + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "colorette": "^1.2.1", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" } }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz", - "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==", - "dev": true, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + "object.assign": "^4.1.0" } }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz", - "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==", + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" - } + "optional": true }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz", - "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==", + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "fill-range": "^7.0.1" } }, - "@babel/plugin-syntax-class-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", - "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" } }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001124", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001124.tgz", + "integrity": "sha512-zQW8V3CdND7GHRH6rxm6s59Ww4g/qGWTheoboW9nfeMg7sUoopIfKCcNZUjwYRCOrvereh3kwDpZj4VLQ7zGtA==", + "dev": true + }, + "chokidar": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", "dev": true, + "optional": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" } }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "colorette": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", + "dev": true + }, + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true + }, + "create-react-context": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz", + "integrity": "sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "gud": "^1.0.0", + "warning": "^4.0.3" } }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", - "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", + "css-loader": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", + "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "camelcase": "^5.3.1", + "cssesc": "^3.0.0", + "icss-utils": "^4.1.1", + "loader-utils": "^1.2.3", + "normalize-path": "^3.0.0", + "postcss": "^7.0.32", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^3.0.2", + "postcss-modules-scope": "^2.2.0", + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^2.7.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz", - "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==", - "dev": true, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.10.4" + "ms": "^2.1.1" } }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz", - "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==", + "ejs": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.5.tgz", + "integrity": "sha512-dldq3ZfFtgVTJMLjOe+/3sROTzALlL9E34V4/sDtUd/KlBSS0s6U1/+WPE1B4sj9CXHJpL1M6rhNJnc9Wbal9w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "jake": "^10.6.1" } }, - "@babel/plugin-transform-classes": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz", - "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==", + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "enhanced-resolve": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", + "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-define-map": "^7.10.4", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4", - "globals": "^11.1.0" + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + } } }, - "@babel/plugin-transform-computed-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz", - "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==", + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "to-regex-range": "^5.0.1" } }, - "@babel/plugin-transform-destructuring": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz", - "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==", + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + } } }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz", - "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==", + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "dependencies": { + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + } } }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz", - "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==", + "fs-extra": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" } }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz", - "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==", + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } + "optional": true }, - "@babel/plugin-transform-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz", - "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==", + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, - "@babel/plugin-transform-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz", - "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==", + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", "dev": true, + "optional": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "is-glob": "^4.0.1" } }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz", - "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==", + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "highlight.js": { + "version": "9.15.10", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.10.tgz", + "integrity": "sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, + "optional": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "binary-extensions": "^2.0.0" } }, - "@babel/plugin-transform-modules-amd": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", - "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", + "is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, + "optional": true, "requires": { - "@babel/helper-module-transforms": "^7.10.5", - "@babel/helper-plugin-utils": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" + "is-extglob": "^2.1.1" } }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz", - "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==", + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-simple-access": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" + "has-symbols": "^1.0.1" } }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", - "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.10.4", - "@babel/helper-module-transforms": "^7.10.5", - "@babel/helper-plugin-utils": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" + "has-symbols": "^1.0.1" } }, - "@babel/plugin-transform-modules-umd": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", - "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", + "isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", + "dev": true + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "minimist": "^1.2.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } } }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", - "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", + "jsonfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", + "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4" + "graceful-fs": "^4.1.6", + "universalify": "^1.0.0" } }, - "@babel/plugin-transform-new-target": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", - "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } } }, - "@babel/plugin-transform-object-super": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", - "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, - "@babel/plugin-transform-parameters": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", - "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", + "lowlight": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.12.1.tgz", + "integrity": "sha512-OqaVxMGIESnawn+TU/QMV5BJLbUghUfjDWPAtFqDYDmDtr4FnB+op8xM+pR7nKlauHNUHXGt0VgWatFB8voS5w==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "fault": "^1.0.2", + "highlight.js": "~9.15.0" } }, - "@babel/plugin-transform-property-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", - "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "yallist": "^3.0.2" } }, - "@babel/plugin-transform-regenerator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", - "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "requires": { - "regenerator-transform": "^0.14.2" + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, - "@babel/plugin-transform-reserved-words": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", - "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", + "markdown-to-jsx": { + "version": "6.11.4", + "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-6.11.4.tgz", + "integrity": "sha512-3lRCD5Sh+tfA52iGgfs/XZiw33f7fFX9Bn55aNnVNUd2GzLDkOWyKYYD8Yju2B1Vn+feiEdgJs8T6Tg0xNokPw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "prop-types": "^15.6.2", + "unquote": "^1.1.0" } }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", - "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-regex": "^7.10.4" + "braces": "^3.0.1", + "picomatch": "^2.0.5" } }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", - "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", - "dev": true, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "dev": true + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "mime-db": "1.44.0" } }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", - "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/preset-env": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz", - "integrity": "sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.11.0", - "@babel/helper-compilation-targets": "^7.10.4", - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-proposal-async-generator-functions": "^7.10.4", - "@babel/plugin-proposal-class-properties": "^7.10.4", - "@babel/plugin-proposal-dynamic-import": "^7.10.4", - "@babel/plugin-proposal-export-namespace-from": "^7.10.4", - "@babel/plugin-proposal-json-strings": "^7.10.4", - "@babel/plugin-proposal-logical-assignment-operators": "^7.11.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", - "@babel/plugin-proposal-numeric-separator": "^7.10.4", - "@babel/plugin-proposal-object-rest-spread": "^7.11.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", - "@babel/plugin-proposal-optional-chaining": "^7.11.0", - "@babel/plugin-proposal-private-methods": "^7.10.4", - "@babel/plugin-proposal-unicode-property-regex": "^7.10.4", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.10.4", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.10.4", - "@babel/plugin-transform-arrow-functions": "^7.10.4", - "@babel/plugin-transform-async-to-generator": "^7.10.4", - "@babel/plugin-transform-block-scoped-functions": "^7.10.4", - "@babel/plugin-transform-block-scoping": "^7.10.4", - "@babel/plugin-transform-classes": "^7.10.4", - "@babel/plugin-transform-computed-properties": "^7.10.4", - "@babel/plugin-transform-destructuring": "^7.10.4", - "@babel/plugin-transform-dotall-regex": "^7.10.4", - "@babel/plugin-transform-duplicate-keys": "^7.10.4", - "@babel/plugin-transform-exponentiation-operator": "^7.10.4", - "@babel/plugin-transform-for-of": "^7.10.4", - "@babel/plugin-transform-function-name": "^7.10.4", - "@babel/plugin-transform-literals": "^7.10.4", - "@babel/plugin-transform-member-expression-literals": "^7.10.4", - "@babel/plugin-transform-modules-amd": "^7.10.4", - "@babel/plugin-transform-modules-commonjs": "^7.10.4", - "@babel/plugin-transform-modules-systemjs": "^7.10.4", - "@babel/plugin-transform-modules-umd": "^7.10.4", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4", - "@babel/plugin-transform-new-target": "^7.10.4", - "@babel/plugin-transform-object-super": "^7.10.4", - "@babel/plugin-transform-parameters": "^7.10.4", - "@babel/plugin-transform-property-literals": "^7.10.4", - "@babel/plugin-transform-regenerator": "^7.10.4", - "@babel/plugin-transform-reserved-words": "^7.10.4", - "@babel/plugin-transform-shorthand-properties": "^7.10.4", - "@babel/plugin-transform-spread": "^7.11.0", - "@babel/plugin-transform-sticky-regex": "^7.10.4", - "@babel/plugin-transform-template-literals": "^7.10.4", - "@babel/plugin-transform-typeof-symbol": "^7.10.4", - "@babel/plugin-transform-unicode-escapes": "^7.10.4", - "@babel/plugin-transform-unicode-regex": "^7.10.4", - "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.11.5", - "browserslist": "^4.12.0", - "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", - "semver": "^5.5.0" + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" }, "dependencies": { - "@babel/plugin-proposal-class-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", - "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz", - "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz", - "integrity": "sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz", - "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", - "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz", - "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", - "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true } } }, - "@babel/register": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.11.5.tgz", - "integrity": "sha512-CAml0ioKX+kOAvBQDHa/+t1fgOt3qkTIz0TrRtRAT6XY0m5qYZXR85k6/sLCNPMGhYDlCFHCYuU0ybTJbvlC6w==", - "dev": true, - "requires": { - "find-cache-dir": "^2.0.0", - "lodash": "^4.17.19", - "make-dir": "^2.1.0", - "pirates": "^4.0.0", - "source-map-support": "^0.5.16" - } + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" + "p-try": "^2.0.0" } }, - "@babel/traverse": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", - "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.5", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.11.5", - "@babel/types": "^7.11.5", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" + "p-limit": "^2.0.0" } }, - "@babel/types": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", - "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" + "find-up": "^4.0.0" } }, - "@emotion/is-prop-valid": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", - "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "polished": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/polished/-/polished-3.6.6.tgz", + "integrity": "sha512-yiB2ims2DZPem0kCD6V0wnhcVGFEhNh0Iw0axNpKU+oSAgFt6yx6HxIT23Qg0WWvgS379cS35zT4AOyZZRzpQQ==", "dev": true, "requires": { - "@emotion/memoize": "0.7.4" + "@babel/runtime": "^7.9.2" } }, - "@emotion/memoize": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", "dev": true }, - "@reach/router": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@reach/router/-/router-1.3.4.tgz", - "integrity": "sha512-+mtn9wjlB9NN2CNnnC/BRYtwdKBfSyyasPYraNAyvaV1occr/5NnB4CVzjEZipNHwYebQwcndGUmpFzxAUoqSA==", + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { - "create-react-context": "0.3.0", - "invariant": "^2.2.3", - "prop-types": "^15.6.1", - "react-lifecycles-compat": "^3.0.4" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "@storybook/addons": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.0.21.tgz", - "integrity": "sha512-yDttNLc3vXqBxwK795ykgzTC6MpvuXDQuF4LHSlHZQe6wsMu1m3fljnbYdafJWdx6cNZwUblU3KYcR11PqhkPg==", - "dev": true, - "requires": { - "@storybook/api": "6.0.21", - "@storybook/channels": "6.0.21", - "@storybook/client-logger": "6.0.21", - "@storybook/core-events": "6.0.21", - "@storybook/router": "6.0.21", - "@storybook/theming": "6.0.21", - "core-js": "^3.0.1", - "global": "^4.3.2", - "regenerator-runtime": "^0.13.3" - } + "qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", + "dev": true }, - "@storybook/api": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/api/-/api-6.0.21.tgz", - "integrity": "sha512-cRRGf/KGFwYiDouTouEcDdp45N1AbYnAfvLqYZ3KuUTGZ+CiU/PN/vavkp07DQeM4FIQO8TLhzHdsLFpLT7Lkw==", + "react-popper-tooltip": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/react-popper-tooltip/-/react-popper-tooltip-2.11.1.tgz", + "integrity": "sha512-04A2f24GhyyMicKvg/koIOQ5BzlrRbKiAgP6L+Pdj1MVX3yJ1NeZ8+EidndQsbejFT55oW1b++wg2Z8KlAyhfQ==", "dev": true, "requires": { - "@reach/router": "^1.3.3", - "@storybook/channels": "6.0.21", - "@storybook/client-logger": "6.0.21", - "@storybook/core-events": "6.0.21", - "@storybook/csf": "0.0.1", - "@storybook/router": "6.0.21", - "@storybook/semver": "^7.3.2", - "@storybook/theming": "6.0.21", - "@types/reach__router": "^1.3.5", - "core-js": "^3.0.1", - "fast-deep-equal": "^3.1.1", - "global": "^4.3.2", - "lodash": "^4.17.15", - "memoizerific": "^1.11.3", - "react": "^16.8.3", - "regenerator-runtime": "^0.13.3", - "store2": "^2.7.1", - "telejson": "^5.0.2", - "ts-dedent": "^1.1.1", - "util-deprecate": "^1.0.2" + "@babel/runtime": "^7.9.2", + "react-popper": "^1.3.7" } }, - "@storybook/channel-postmessage": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.0.21.tgz", - "integrity": "sha512-ArRnoaS+b7qpAku/SO27z/yjRDCXb37mCPYGX0ntPbiQajootUbGO7otfnjFkaP44hCEC9uDYlOfMU1hYU1N6A==", + "react-syntax-highlighter": { + "version": "12.2.1", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz", + "integrity": "sha512-CTsp0ZWijwKRYFg9xhkWD4DSpQqE4vb2NKVMdPAkomnILSmsNBHE0n5GuI5zB+PU3ySVvXvdt9jo+ViD9XibCA==", "dev": true, "requires": { - "@storybook/channels": "6.0.21", - "@storybook/client-logger": "6.0.21", - "@storybook/core-events": "6.0.21", - "core-js": "^3.0.1", - "global": "^4.3.2", - "qs": "^6.6.0", - "telejson": "^5.0.2" + "@babel/runtime": "^7.3.1", + "highlight.js": "~9.15.1", + "lowlight": "1.12.1", + "prismjs": "^1.8.4", + "refractor": "^2.4.1" } }, - "@storybook/channels": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.0.21.tgz", - "integrity": "sha512-G6gjcEotSwDmOlxSmOMgsO3VhQ42RLJK7kFp6D5eg0Q6S8vsypltdT8orxdu+6+AbcBrL+5Sla8lThzaCvXsVQ==", + "react-textarea-autosize": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.2.0.tgz", + "integrity": "sha512-grajUlVbkx6VdtSxCgzloUIphIZF5bKr21OYMceWPKkniy7H0mRAT/AXPrRtObAe+zUePnNlBwUc4ivVjUGIjw==", "dev": true, "requires": { - "core-js": "^3.0.1", - "ts-dedent": "^1.1.1", - "util-deprecate": "^1.0.2" + "@babel/runtime": "^7.10.2", + "use-composed-ref": "^1.0.0", + "use-latest": "^1.0.0" } }, - "@storybook/client-api": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.0.21.tgz", - "integrity": "sha512-emBXd/ml6pc3G8gP3MsR9zQsAq1zZbqof9MxB51tG/jpTXdqWQ8ce1pt1tJS8Xj0QDM072jR6wsY+mmro0GZnA==", + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", "dev": true, + "optional": true, "requires": { - "@storybook/addons": "6.0.21", - "@storybook/channel-postmessage": "6.0.21", - "@storybook/channels": "6.0.21", - "@storybook/client-logger": "6.0.21", - "@storybook/core-events": "6.0.21", - "@storybook/csf": "0.0.1", - "@types/qs": "^6.9.0", - "@types/webpack-env": "^1.15.2", - "core-js": "^3.0.1", - "global": "^4.3.2", - "lodash": "^4.17.15", - "memoizerific": "^1.11.3", - "qs": "^6.6.0", - "stable": "^0.1.8", - "store2": "^2.7.1", - "ts-dedent": "^1.1.1", - "util-deprecate": "^1.0.2" + "picomatch": "^2.2.1" + }, + "dependencies": { + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true, + "optional": true + } } }, - "@storybook/client-logger": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.0.21.tgz", - "integrity": "sha512-8aUEbhjXV+UMYQWukVYnp+kZafF+LD4Dm7eMo37IUZvt3VIjV1VvhxIDVJtqjk2vv0KZTepESFBkZQLmBzI9Zg==", + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { - "core-js": "^3.0.1", - "global": "^4.3.2" + "glob": "^7.1.3" } }, - "@storybook/components": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-6.0.21.tgz", - "integrity": "sha512-r6btqFW/rcXIU5v231EifZfdh9O0fy7bJDXwwDf8zVUgLx8JRc0VnSs3nvK3Is9HF1wZ9vjx/7Lh4rTIDZAjgg==", + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", "dev": true, "requires": { - "@storybook/client-logger": "6.0.21", - "@storybook/csf": "0.0.1", - "@storybook/theming": "6.0.21", - "@types/overlayscrollbars": "^1.9.0", - "@types/react-color": "^3.0.1", - "@types/react-syntax-highlighter": "11.0.4", - "core-js": "^3.0.1", - "fast-deep-equal": "^3.1.1", - "global": "^4.3.2", - "lodash": "^4.17.15", - "markdown-to-jsx": "^6.11.4", - "memoizerific": "^1.11.3", - "overlayscrollbars": "^1.10.2", - "polished": "^3.4.4", - "popper.js": "^1.14.7", - "react": "^16.8.3", - "react-color": "^2.17.0", - "react-dom": "^16.8.3", - "react-popper-tooltip": "^2.11.0", - "react-syntax-highlighter": "^12.2.1", - "react-textarea-autosize": "^8.1.1", - "ts-dedent": "^1.1.1" + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" } }, - "@storybook/core-events": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.0.21.tgz", - "integrity": "sha512-p84fbPcsAhnqDhp+HJ4P8+vI2BqJus4IRoVAemLAwuPjyPElrV9UvOa/RHy1BN8Z6jXwFA+FFzfGl2kPJ3WYcA==", + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", "dev": true, "requires": { - "core-js": "^3.0.1" + "randombytes": "^2.1.0" } }, - "@storybook/router": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.0.21.tgz", - "integrity": "sha512-46SsKJfcd12lRrISnfrWhicJx8EylkgGDGohfH0n5p7inkkGOkKV8QFZoYPRKZueMXmUKpzJ0Z3HmVsLTCrCDw==", + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", "dev": true, "requires": { - "@reach/router": "^1.3.3", - "@types/reach__router": "^1.3.5", - "core-js": "^3.0.1", - "global": "^4.3.2", - "memoizerific": "^1.11.3", - "qs": "^6.6.0" + "figgy-pudding": "^3.5.1" } }, - "@storybook/semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", + "style-loader": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.2.1.tgz", + "integrity": "sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg==", "dev": true, "requires": { - "core-js": "^3.6.5", - "find-up": "^4.1.0" + "loader-utils": "^2.0.0", + "schema-utils": "^2.6.6" }, "dependencies": { - "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", - "dev": true + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } } } }, - "@storybook/theming": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.0.21.tgz", - "integrity": "sha512-n97DfB9kG6WrV1xBGDyeQibTrh8pBBCp3dSL3UTGH+KX3C2+4sm6QHlTgyekbi5FrbFEbnuZOKAS3YbLVONsRQ==", - "dev": true, - "requires": { - "@emotion/core": "^10.0.20", - "@emotion/is-prop-valid": "^0.8.6", - "@emotion/styled": "^10.0.17", - "@storybook/client-logger": "6.0.21", - "core-js": "^3.0.1", - "deep-object-diff": "^1.1.0", - "emotion-theming": "^10.0.19", - "global": "^4.3.2", - "memoizerific": "^1.11.3", - "polished": "^3.4.4", - "resolve-from": "^5.0.0", - "ts-dedent": "^1.1.1" - } - }, - "@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", "dev": true }, - "@types/reach__router": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/reach__router/-/reach__router-1.3.5.tgz", - "integrity": "sha512-h0NbqXN/tJuBY/xggZSej1SKQEstbHO7J/omt1tYoFGmj3YXOodZKbbqD4mNDh7zvEGYd7YFrac1LTtAr3xsYQ==", + "telejson": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/telejson/-/telejson-5.0.2.tgz", + "integrity": "sha512-XCrDHGbinczsscs8LXFr9jDhvy37yBk9piB7FJrCfxE8oP66WDkolNMpaBkWYgQqB9dQGBGtTDzGQPedc9KJmw==", "dev": true, "requires": { - "@types/history": "*", - "@types/react": "*" + "@types/is-function": "^1.0.0", + "global": "^4.4.0", + "is-function": "^1.0.2", + "is-regex": "^1.1.1", + "is-symbol": "^1.0.3", + "isobject": "^4.0.0", + "lodash": "^4.17.19", + "memoizerific": "^1.11.3" } }, - "@types/react-syntax-highlighter": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.4.tgz", - "integrity": "sha512-9GfTo3a0PHwQeTVoqs0g5bS28KkSY48pp5659wA+Dp4MqceDEa8EHBqrllJvvtyusszyJhViUEap0FDvlk/9Zg==", + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "@types/react": "*" + "is-number": "^7.0.0" } }, - "@webassemblyjs/ast": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", - "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "ts-dedent": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-1.1.1.tgz", + "integrity": "sha512-UGTRZu1evMw4uTPyYF66/KFd22XiU+jMaIuHrkIHQ2GivAXVlLV0v/vHrpOuTRf9BmpNHi/SO7Vd0rLu0y57jg==", + "dev": true + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", "dev": true, "requires": { - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0" + "unique-slug": "^2.0.0" } }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", - "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", - "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", "dev": true }, - "@webassemblyjs/helper-buffer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", - "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", - "dev": true + "url-loader": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.0.tgz", + "integrity": "sha512-IzgAAIC8wRrg6NYkFIJY09vtktQcsvU8V6HhtQj9PTefbYImzLB1hufqo4m+RyM5N3mLx5BqJKccgxJS+W3kqw==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.26", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } + } }, - "@webassemblyjs/helper-code-frame": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", - "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", "dev": true, "requires": { - "@webassemblyjs/wast-printer": "1.9.0" + "loose-envify": "^1.0.0" } }, - "@webassemblyjs/helper-fsm": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", - "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", - "dev": true - }, - "@webassemblyjs/helper-module-context": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", - "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", + "watchpack": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz", + "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0" + "chokidar": "^3.4.1", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.0" } }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", - "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", - "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "webpack": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.1.tgz", + "integrity": "sha512-4UOGAohv/VGUNQJstzEywwNxqX417FnjZgZJpJQegddzPmTvph37eBIRbRTfdySXzVtJXLJfbMN3mMYhM6GdmQ==", "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", - "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", - "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^6.4.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^4.3.0", + "eslint-scope": "^4.0.3", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.3", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", + "schema-utils": "^1.0.0", + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.3", + "watchpack": "^1.7.4", + "webpack-sources": "^1.4.1" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "terser-webpack-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "dev": true, + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } } }, - "@webassemblyjs/leb128": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", - "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", "dev": true, "requires": { - "@xtuc/long": "4.2.2" + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, - "@webassemblyjs/utf8": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", - "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "dev": true }, - "@webassemblyjs/wasm-edit": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", - "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "@storybook/core-events": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-5.3.2.tgz", + "integrity": "sha512-a1zVQqN8SMMAbdq0OV6Pc130VNasFYP85HO72VJf8t1aZGq40lYKFiALuF2S3Ax4ZIvJFbSrLM9OCpNNYg/ung==", + "dev": true, + "requires": { + "core-js": "^3.0.1" + } + }, + "@storybook/csf": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.1.tgz", + "integrity": "sha512-USTLkZze5gkel8MYCujSRBVIrUQ3YPBrLOx7GNk/0wttvVtlzWXAq9eLbQ4p/NicGxP+3T7KPEMVV//g+yubpw==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + }, + "@storybook/node-logger": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.0.21.tgz", + "integrity": "sha512-KRBf+Fz7fgtwHdnYt70JTZbcYMZ1pQPtDyqbrFYCjwkbx5GPX5vMOozlxCIj9elseqPIsF8CKgHOW7cFHVyWYw==", + "dev": true, + "requires": { + "@types/npmlog": "^4.1.2", + "chalk": "^4.0.0", + "core-js": "^3.0.1", + "npmlog": "^4.1.2", + "pretty-hrtime": "^1.0.3" + } + }, + "@storybook/postinstall": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-5.3.2.tgz", + "integrity": "sha512-SHDXiubFKSoIpV5E2R9K104Ye4qhRQuoN9SKCB400Ya2JyBSGz5FMuNVmRpO8JqJrUe9uI1d/k18/Mma1/73YA==", + "dev": true, + "requires": { + "core-js": "^3.0.1" + } + }, + "@storybook/react": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-6.0.21.tgz", + "integrity": "sha512-L3PcoBJq5aK1aTaJNfwsSJ8Kxgcyk0WknN4TDqhP7a+oXmuMY1YEi96hEvQVIm0TBCkQxs61K70/T7vlilEtHg==", + "dev": true, + "requires": { + "@babel/preset-flow": "^7.0.0", + "@babel/preset-react": "^7.0.0", + "@storybook/addons": "6.0.21", + "@storybook/core": "6.0.21", + "@storybook/node-logger": "6.0.21", + "@storybook/semver": "^7.3.2", + "@svgr/webpack": "^5.4.0", + "@types/webpack-env": "^1.15.2", + "babel-plugin-add-react-displayname": "^0.0.5", + "babel-plugin-named-asset-import": "^0.3.1", + "babel-plugin-react-docgen": "^4.1.0", + "core-js": "^3.0.1", + "global": "^4.3.2", + "lodash": "^4.17.15", + "prop-types": "^15.7.2", + "react-dev-utils": "^10.0.0", + "react-docgen-typescript-plugin": "^0.5.2", + "regenerator-runtime": "^0.13.3", + "ts-dedent": "^1.1.1", + "webpack": "^4.43.0" + }, + "dependencies": { + "@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/helper-wasm-section": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-opt": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "@webassemblyjs/wast-printer": "1.9.0" + "@emotion/memoize": "0.7.4" } }, - "@webassemblyjs/wasm-gen": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", - "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "dev": true + }, + "@reach/router": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@reach/router/-/router-1.3.4.tgz", + "integrity": "sha512-+mtn9wjlB9NN2CNnnC/BRYtwdKBfSyyasPYraNAyvaV1occr/5NnB4CVzjEZipNHwYebQwcndGUmpFzxAUoqSA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" + "create-react-context": "0.3.0", + "invariant": "^2.2.3", + "prop-types": "^15.6.1", + "react-lifecycles-compat": "^3.0.4" } }, - "@webassemblyjs/wasm-opt": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", - "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "@storybook/addons": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.0.21.tgz", + "integrity": "sha512-yDttNLc3vXqBxwK795ykgzTC6MpvuXDQuF4LHSlHZQe6wsMu1m3fljnbYdafJWdx6cNZwUblU3KYcR11PqhkPg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0" + "@storybook/api": "6.0.21", + "@storybook/channels": "6.0.21", + "@storybook/client-logger": "6.0.21", + "@storybook/core-events": "6.0.21", + "@storybook/router": "6.0.21", + "@storybook/theming": "6.0.21", + "core-js": "^3.0.1", + "global": "^4.3.2", + "regenerator-runtime": "^0.13.3" } }, - "@webassemblyjs/wasm-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", - "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "@storybook/api": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/api/-/api-6.0.21.tgz", + "integrity": "sha512-cRRGf/KGFwYiDouTouEcDdp45N1AbYnAfvLqYZ3KuUTGZ+CiU/PN/vavkp07DQeM4FIQO8TLhzHdsLFpLT7Lkw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" + "@reach/router": "^1.3.3", + "@storybook/channels": "6.0.21", + "@storybook/client-logger": "6.0.21", + "@storybook/core-events": "6.0.21", + "@storybook/csf": "0.0.1", + "@storybook/router": "6.0.21", + "@storybook/semver": "^7.3.2", + "@storybook/theming": "6.0.21", + "@types/reach__router": "^1.3.5", + "core-js": "^3.0.1", + "fast-deep-equal": "^3.1.1", + "global": "^4.3.2", + "lodash": "^4.17.15", + "memoizerific": "^1.11.3", + "react": "^16.8.3", + "regenerator-runtime": "^0.13.3", + "store2": "^2.7.1", + "telejson": "^5.0.2", + "ts-dedent": "^1.1.1", + "util-deprecate": "^1.0.2" } }, - "@webassemblyjs/wast-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", - "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "@storybook/channels": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.0.21.tgz", + "integrity": "sha512-G6gjcEotSwDmOlxSmOMgsO3VhQ42RLJK7kFp6D5eg0Q6S8vsypltdT8orxdu+6+AbcBrL+5Sla8lThzaCvXsVQ==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/floating-point-hex-parser": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-code-frame": "1.9.0", - "@webassemblyjs/helper-fsm": "1.9.0", - "@xtuc/long": "4.2.2" + "core-js": "^3.0.1", + "ts-dedent": "^1.1.1", + "util-deprecate": "^1.0.2" } }, - "@webassemblyjs/wast-printer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", - "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "@storybook/client-logger": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.0.21.tgz", + "integrity": "sha512-8aUEbhjXV+UMYQWukVYnp+kZafF+LD4Dm7eMo37IUZvt3VIjV1VvhxIDVJtqjk2vv0KZTepESFBkZQLmBzI9Zg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0", - "@xtuc/long": "4.2.2" + "core-js": "^3.0.1", + "global": "^4.3.2" } }, - "ajv": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", - "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "@storybook/core-events": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.0.21.tgz", + "integrity": "sha512-p84fbPcsAhnqDhp+HJ4P8+vI2BqJus4IRoVAemLAwuPjyPElrV9UvOa/RHy1BN8Z6jXwFA+FFzfGl2kPJ3WYcA==", "dev": true, "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "core-js": "^3.0.1" } }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true - }, - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "@storybook/router": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.0.21.tgz", + "integrity": "sha512-46SsKJfcd12lRrISnfrWhicJx8EylkgGDGohfH0n5p7inkkGOkKV8QFZoYPRKZueMXmUKpzJ0Z3HmVsLTCrCDw==", "dev": true, - "optional": true, "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@reach/router": "^1.3.3", + "@types/reach__router": "^1.3.5", + "core-js": "^3.0.1", + "global": "^4.3.2", + "memoizerific": "^1.11.3", + "qs": "^6.6.0" } }, - "autoprefixer": { - "version": "9.8.6", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", - "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", + "@storybook/semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", "dev": true, "requires": { - "browserslist": "^4.12.0", - "caniuse-lite": "^1.0.30001109", - "colorette": "^1.2.1", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^7.0.32", - "postcss-value-parser": "^4.1.0" + "core-js": "^3.6.5", + "find-up": "^4.1.0" + }, + "dependencies": { + "core-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", + "dev": true + } } }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "@storybook/theming": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.0.21.tgz", + "integrity": "sha512-n97DfB9kG6WrV1xBGDyeQibTrh8pBBCp3dSL3UTGH+KX3C2+4sm6QHlTgyekbi5FrbFEbnuZOKAS3YbLVONsRQ==", "dev": true, "requires": { - "object.assign": "^4.1.0" - } - }, + "@emotion/core": "^10.0.20", + "@emotion/is-prop-valid": "^0.8.6", + "@emotion/styled": "^10.0.17", + "@storybook/client-logger": "6.0.21", + "core-js": "^3.0.1", + "deep-object-diff": "^1.1.0", + "emotion-theming": "^10.0.19", + "global": "^4.3.2", + "memoizerific": "^1.11.3", + "polished": "^3.4.4", + "resolve-from": "^5.0.0", + "ts-dedent": "^1.1.1" + } + }, + "@svgr/babel-plugin-add-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==", + "dev": true + }, + "@svgr/babel-plugin-remove-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==", + "dev": true + }, + "@svgr/babel-plugin-svg-dynamic-title": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", + "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==", + "dev": true + }, + "@svgr/babel-plugin-svg-em-dimensions": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", + "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==", + "dev": true + }, + "@svgr/babel-plugin-transform-react-native-svg": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", + "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==", + "dev": true + }, + "@svgr/babel-plugin-transform-svg-component": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.4.0.tgz", + "integrity": "sha512-zLl4Fl3NvKxxjWNkqEcpdSOpQ3LGVH2BNFQ6vjaK6sFo2IrSznrhURIPI0HAphKiiIwNYjAfE0TNoQDSZv0U9A==", + "dev": true + }, + "@svgr/babel-preset": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.4.0.tgz", + "integrity": "sha512-Gyx7cCxua04DBtyILTYdQxeO/pwfTBev6+eXTbVbxe4HTGhOUW6yo7PSbG2p6eJMl44j6XSequ0ZDP7bl0nu9A==", + "dev": true, + "requires": { + "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", + "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", + "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", + "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", + "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", + "@svgr/babel-plugin-transform-svg-component": "^5.4.0" + } + }, + "@svgr/core": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.4.0.tgz", + "integrity": "sha512-hWGm1DCCvd4IEn7VgDUHYiC597lUYhFau2lwJBYpQWDirYLkX4OsXu9IslPgJ9UpP7wsw3n2Ffv9sW7SXJVfqQ==", + "dev": true, + "requires": { + "@svgr/plugin-jsx": "^5.4.0", + "camelcase": "^6.0.0", + "cosmiconfig": "^6.0.0" + } + }, + "@svgr/hast-util-to-babel-ast": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.4.0.tgz", + "integrity": "sha512-+U0TZZpPsP2V1WvVhqAOSTk+N+CjYHdZx+x9UBa1eeeZDXwH8pt0CrQf2+SvRl/h2CAPRFkm+Ey96+jKP8Bsgg==", + "dev": true, + "requires": { + "@babel/types": "^7.9.5" + } + }, + "@svgr/plugin-jsx": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.4.0.tgz", + "integrity": "sha512-SGzO4JZQ2HvGRKDzRga9YFSqOqaNrgLlQVaGvpZ2Iht2gwRp/tq+18Pvv9kS9ZqOMYgyix2LLxZMY1LOe9NPqw==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@svgr/babel-preset": "^5.4.0", + "@svgr/hast-util-to-babel-ast": "^5.4.0", + "svg-parser": "^2.0.2" + } + }, + "@svgr/plugin-svgo": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.4.0.tgz", + "integrity": "sha512-3Cgv3aYi1l6SHyzArV9C36yo4kgwVdF3zPQUC6/aCDUeXAofDYwE5kk3e3oT5ZO2a0N3lB+lLGvipBG6lnG8EA==", + "dev": true, + "requires": { + "cosmiconfig": "^6.0.0", + "merge-deep": "^3.0.2", + "svgo": "^1.2.2" + } + }, + "@svgr/webpack": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.4.0.tgz", + "integrity": "sha512-LjepnS/BSAvelnOnnzr6Gg0GcpLmnZ9ThGFK5WJtm1xOqdBE/1IACZU7MMdVzjyUkfFqGz87eRE4hFaSLiUwYg==", + "dev": true, + "requires": { + "@babel/core": "^7.9.0", + "@babel/plugin-transform-react-constant-elements": "^7.9.0", + "@babel/preset-env": "^7.9.5", + "@babel/preset-react": "^7.9.4", + "@svgr/core": "^5.4.0", + "@svgr/plugin-jsx": "^5.4.0", + "@svgr/plugin-svgo": "^5.4.0", + "loader-utils": "^2.0.0" + } + }, + "@types/reach__router": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/reach__router/-/reach__router-1.3.5.tgz", + "integrity": "sha512-h0NbqXN/tJuBY/xggZSej1SKQEstbHO7J/omt1tYoFGmj3YXOodZKbbqD4mNDh7zvEGYd7YFrac1LTtAr3xsYQ==", + "dev": true, + "requires": { + "@types/history": "*", + "@types/react": "*" + } + }, + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", + "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", + "dev": true + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", + "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", + "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", + "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", + "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/floating-point-hex-parser": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-code-frame": "1.9.0", + "@webassemblyjs/helper-fsm": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "optional": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "requires": { + "object.assign": "^4.1.0" + } + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -9854,6 +9751,7 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, + "optional": true, "requires": { "fill-range": "^7.0.1" } @@ -9879,19 +9777,32 @@ "ssri": "^6.0.1", "unique-filename": "^1.1.1", "y18n": "^4.0.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + } } }, "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", + "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", "dev": true }, - "caniuse-lite": { - "version": "1.0.30001124", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001124.tgz", - "integrity": "sha512-zQW8V3CdND7GHRH6rxm6s59Ww4g/qGWTheoboW9nfeMg7sUoopIfKCcNZUjwYRCOrvereh3kwDpZj4VLQ7zGtA==", - "dev": true + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } }, "chokidar": { "version": "3.4.2", @@ -9916,17 +9827,18 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true }, - "colorette": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", - "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", - "dev": true - }, - "commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "dev": true + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } }, "create-react-context": { "version": "0.3.0", @@ -9938,31 +9850,71 @@ "warning": "^4.0.3" } }, - "css-loader": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", - "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", + "css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", "dev": true, "requires": { - "camelcase": "^5.3.1", - "cssesc": "^3.0.0", - "icss-utils": "^4.1.1", - "loader-utils": "^1.2.3", - "normalize-path": "^3.0.0", - "postcss": "^7.0.32", - "postcss-modules-extract-imports": "^2.0.0", - "postcss-modules-local-by-default": "^3.0.2", - "postcss-modules-scope": "^2.2.0", - "postcss-modules-values": "^3.0.0", - "postcss-value-parser": "^4.1.0", - "schema-utils": "^2.7.0", - "semver": "^6.3.0" + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dev": true, + "requires": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" }, "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "css-what": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.3.0.tgz", + "integrity": "sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg==", + "dev": true + }, + "csso": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.3.tgz", + "integrity": "sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==", + "dev": true, + "requires": { + "css-tree": "1.0.0-alpha.39" + }, + "dependencies": { + "css-tree": { + "version": "1.0.0-alpha.39", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz", + "integrity": "sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==", + "dev": true, + "requires": { + "mdn-data": "2.0.6", + "source-map": "^0.6.1" + } + }, + "mdn-data": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz", + "integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } @@ -9971,18 +9923,27 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, "requires": { "ms": "^2.1.1" } }, - "ejs": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.5.tgz", - "integrity": "sha512-dldq3ZfFtgVTJMLjOe+/3sROTzALlL9E34V4/sDtUd/KlBSS0s6U1/+WPE1B4sj9CXHJpL1M6rhNJnc9Wbal9w==", + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "jake": "^10.6.1" + "object-keys": "^1.0.12" + } + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" } }, "emojis-list": { @@ -10014,52 +9975,71 @@ } } }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", "dev": true, "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + }, "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true } } }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "optional": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -10068,44 +10048,6 @@ "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" - }, - "dependencies": { - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - } - } - }, - "fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^1.0.0" } }, "fsevents": { @@ -10142,14 +10084,7 @@ "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "has-symbols": { "version": "1.0.1", @@ -10157,12 +10092,6 @@ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", "dev": true }, - "highlight.js": { - "version": "9.15.10", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.10.tgz", - "integrity": "sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw==", - "dev": true - }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -10173,6 +10102,12 @@ "binary-extensions": "^2.0.0" } }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true + }, "is-function": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", @@ -10193,7 +10128,8 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "dev": true, + "optional": true }, "is-regex": { "version": "1.1.1", @@ -10226,66 +10162,26 @@ "dev": true, "requires": { "minimist": "^1.2.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - } - } - }, - "jsonfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", - "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^1.0.0" } }, "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", "dev": true, "requires": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - } + "json5": "^2.1.2" } }, "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lowlight": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.12.1.tgz", - "integrity": "sha512-OqaVxMGIESnawn+TU/QMV5BJLbUghUfjDWPAtFqDYDmDtr4FnB+op8xM+pR7nKlauHNUHXGt0VgWatFB8voS5w==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "fault": "^1.0.2", - "highlight.js": "~9.15.0" + "p-locate": "^4.1.0" } }, "lru-cache": { @@ -10315,40 +10211,17 @@ } } }, - "markdown-to-jsx": { - "version": "6.11.4", - "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-6.11.4.tgz", - "integrity": "sha512-3lRCD5Sh+tfA52iGgfs/XZiw33f7fFX9Bn55aNnVNUd2GzLDkOWyKYYD8Yju2B1Vn+feiEdgJs8T6Tg0xNokPw==", - "dev": true, - "requires": { - "prop-types": "^15.6.2", - "unquote": "^1.1.0" - } - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", "dev": true }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "dev": true, - "requires": { - "mime-db": "1.44.0" - } + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true }, "mississippi": { "version": "3.0.0", @@ -10368,28 +10241,10 @@ "through2": "^2.0.0" } }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - } - } - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "neo-async": { "version": "2.6.2", @@ -10401,8 +10256,36 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "optional": true + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", "dev": true }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -10413,12 +10296,12 @@ } }, "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "^2.2.0" } }, "p-try": { @@ -10427,6 +10310,30 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "parse-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -10434,12 +10341,48 @@ "dev": true }, "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", "dev": true, "requires": { - "find-up": "^4.0.0" + "find-up": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } } }, "polished": { @@ -10451,12 +10394,6 @@ "@babel/runtime": "^7.9.2" } }, - "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", - "dev": true - }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -10473,51 +10410,6 @@ "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", "dev": true }, - "react-popper-tooltip": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/react-popper-tooltip/-/react-popper-tooltip-2.11.1.tgz", - "integrity": "sha512-04A2f24GhyyMicKvg/koIOQ5BzlrRbKiAgP6L+Pdj1MVX3yJ1NeZ8+EidndQsbejFT55oW1b++wg2Z8KlAyhfQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.9.2", - "react-popper": "^1.3.7" - } - }, - "react-syntax-highlighter": { - "version": "12.2.1", - "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz", - "integrity": "sha512-CTsp0ZWijwKRYFg9xhkWD4DSpQqE4vb2NKVMdPAkomnILSmsNBHE0n5GuI5zB+PU3ySVvXvdt9jo+ViD9XibCA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "highlight.js": "~9.15.1", - "lowlight": "1.12.1", - "prismjs": "^1.8.4", - "refractor": "^2.4.1" - } - }, - "react-textarea-autosize": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.2.0.tgz", - "integrity": "sha512-grajUlVbkx6VdtSxCgzloUIphIZF5bKr21OYMceWPKkniy7H0mRAT/AXPrRtObAe+zUePnNlBwUc4ivVjUGIjw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.10.2", - "use-composed-ref": "^1.0.0", - "use-latest": "^1.0.0" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - } - } - }, "readdirp": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", @@ -10558,17 +10450,6 @@ "glob": "^7.1.3" } }, - "schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - } - }, "serialize-javascript": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", @@ -10587,27 +10468,45 @@ "figgy-pudding": "^3.5.1" } }, - "style-loader": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.2.1.tgz", - "integrity": "sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg==", + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", "dev": true, "requires": { - "loader-utils": "^2.0.0", - "schema-utils": "^2.6.6" - }, - "dependencies": { - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - } + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" } }, "tapable": { @@ -10632,11 +10531,37 @@ "memoizerific": "^1.11.3" } }, + "terser-webpack-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "dev": true, + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "optional": true, "requires": { "is-number": "^7.0.0" } @@ -10656,40 +10581,10 @@ "unique-slug": "^2.0.0" } }, - "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", - "dev": true - }, - "url-loader": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.0.tgz", - "integrity": "sha512-IzgAAIC8wRrg6NYkFIJY09vtktQcsvU8V6HhtQj9PTefbYImzLB1hufqo4m+RyM5N3mLx5BqJKccgxJS+W3kqw==", - "dev": true, - "requires": { - "loader-utils": "^2.0.0", - "mime-types": "^2.1.26", - "schema-utils": "^2.6.5" - }, - "dependencies": { - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - } - } - }, - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", "dev": true, "requires": { "loose-envify": "^1.0.0" @@ -10738,147 +10633,33 @@ "webpack-sources": "^1.4.1" }, "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "minimist": "^1.2.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "terser-webpack-plugin": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", - "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", "dev": true, "requires": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" } }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "minimist": "^1.2.5" } } } @@ -10915,1466 +10696,2368 @@ } } }, - "@storybook/core-events": { + "@storybook/router": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-5.3.2.tgz", - "integrity": "sha512-a1zVQqN8SMMAbdq0OV6Pc130VNasFYP85HO72VJf8t1aZGq40lYKFiALuF2S3Ax4ZIvJFbSrLM9OCpNNYg/ung==", - "dev": true, - "requires": { - "core-js": "^3.0.1" - } - }, - "@storybook/csf": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.1.tgz", - "integrity": "sha512-USTLkZze5gkel8MYCujSRBVIrUQ3YPBrLOx7GNk/0wttvVtlzWXAq9eLbQ4p/NicGxP+3T7KPEMVV//g+yubpw==", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-5.3.2.tgz", + "integrity": "sha512-EeM27i+89WS2mdT4j7RyVMSk7e7QiDb8bmyJAX3qyxg9ZOI4Wrvawgj6OAGuetItC1nayCPXFlXtIfHsP1h3lg==", "dev": true, "requires": { - "lodash": "^4.17.15" + "@reach/router": "^1.2.1", + "@storybook/csf": "0.0.1", + "@types/reach__router": "^1.2.3", + "core-js": "^3.0.1", + "global": "^4.3.2", + "lodash": "^4.17.15", + "memoizerific": "^1.11.3", + "qs": "^6.6.0", + "util-deprecate": "^1.0.2" + }, + "dependencies": { + "qs": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.1.tgz", + "integrity": "sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==", + "dev": true + } } }, - "@storybook/node-logger": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.0.21.tgz", - "integrity": "sha512-KRBf+Fz7fgtwHdnYt70JTZbcYMZ1pQPtDyqbrFYCjwkbx5GPX5vMOozlxCIj9elseqPIsF8CKgHOW7cFHVyWYw==", + "@storybook/source-loader": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-5.3.2.tgz", + "integrity": "sha512-bHEWLXTSn89jip/fy/rjf2Psrmu4ZdquOD/SmB+WVuKRYE8JxcN1ybNUOhM7+XqekzIV/iw9rivhjGO1nZ7HAQ==", "dev": true, "requires": { - "@types/npmlog": "^4.1.2", - "chalk": "^4.0.0", + "@storybook/addons": "5.3.2", + "@storybook/client-logger": "5.3.2", + "@storybook/csf": "0.0.1", "core-js": "^3.0.1", - "npmlog": "^4.1.2", - "pretty-hrtime": "^1.0.3" + "estraverse": "^4.2.0", + "global": "^4.3.2", + "loader-utils": "^1.2.3", + "prettier": "^1.16.4", + "prop-types": "^15.7.2", + "regenerator-runtime": "^0.13.3" + }, + "dependencies": { + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + } + }, + "prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "dev": true + } } }, - "@storybook/postinstall": { + "@storybook/theming": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-5.3.2.tgz", - "integrity": "sha512-SHDXiubFKSoIpV5E2R9K104Ye4qhRQuoN9SKCB400Ya2JyBSGz5FMuNVmRpO8JqJrUe9uI1d/k18/Mma1/73YA==", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-5.3.2.tgz", + "integrity": "sha512-WzFVgE2v/0mlK/5CqM9kSDdCMag7uqFsjI5oe3HCWEpwrUH70ndhuVZTVpyx0fscdL74vT5XZieoy2WwpVBl5Q==", "dev": true, "requires": { - "core-js": "^3.0.1" + "@emotion/core": "^10.0.20", + "@emotion/styled": "^10.0.17", + "@storybook/client-logger": "5.3.2", + "core-js": "^3.0.1", + "deep-object-diff": "^1.1.0", + "emotion-theming": "^10.0.19", + "global": "^4.3.2", + "memoizerific": "^1.11.3", + "polished": "^3.3.1", + "prop-types": "^15.7.2", + "resolve-from": "^5.0.0", + "ts-dedent": "^1.1.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } } }, - "@storybook/react": { + "@storybook/ui": { "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-6.0.21.tgz", - "integrity": "sha512-L3PcoBJq5aK1aTaJNfwsSJ8Kxgcyk0WknN4TDqhP7a+oXmuMY1YEi96hEvQVIm0TBCkQxs61K70/T7vlilEtHg==", + "resolved": "https://registry.npmjs.org/@storybook/ui/-/ui-6.0.21.tgz", + "integrity": "sha512-50QYF8tHUgpVq7B7PWp7kmyf79NySWJO0piQFjHv027vV8GfbXMWVswAXwo3IfCihPlnLKe01WbsigM/9T1HCQ==", "dev": true, "requires": { - "@babel/preset-flow": "^7.0.0", - "@babel/preset-react": "^7.0.0", + "@emotion/core": "^10.0.20", "@storybook/addons": "6.0.21", - "@storybook/core": "6.0.21", - "@storybook/node-logger": "6.0.21", + "@storybook/api": "6.0.21", + "@storybook/channels": "6.0.21", + "@storybook/client-logger": "6.0.21", + "@storybook/components": "6.0.21", + "@storybook/core-events": "6.0.21", + "@storybook/router": "6.0.21", "@storybook/semver": "^7.3.2", - "@svgr/webpack": "^5.4.0", - "@types/webpack-env": "^1.15.2", - "babel-plugin-add-react-displayname": "^0.0.5", - "babel-plugin-named-asset-import": "^0.3.1", - "babel-plugin-react-docgen": "^4.1.0", + "@storybook/theming": "6.0.21", + "@types/markdown-to-jsx": "^6.11.0", + "copy-to-clipboard": "^3.0.8", "core-js": "^3.0.1", + "core-js-pure": "^3.0.1", + "emotion-theming": "^10.0.19", + "fuse.js": "^3.6.1", "global": "^4.3.2", "lodash": "^4.17.15", - "prop-types": "^15.7.2", - "react-dev-utils": "^10.0.0", - "react-docgen-typescript-plugin": "^0.5.2", + "markdown-to-jsx": "^6.11.4", + "memoizerific": "^1.11.3", + "polished": "^3.4.4", + "qs": "^6.6.0", + "react": "^16.8.3", + "react-dom": "^16.8.3", + "react-draggable": "^4.0.3", + "react-helmet-async": "^1.0.2", + "react-hotkeys": "2.0.0", + "react-sizeme": "^2.6.7", "regenerator-runtime": "^0.13.3", - "ts-dedent": "^1.1.1", - "webpack": "^4.43.0" + "resolve-from": "^5.0.0", + "store2": "^2.7.1" }, "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", "dev": true, "requires": { - "@babel/highlight": "^7.10.4" + "@emotion/memoize": "0.7.4" } }, - "@babel/compat-data": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz", - "integrity": "sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==", - "dev": true, - "requires": { - "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "semver": "^5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } + "@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "dev": true }, - "@babel/generator": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", - "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "@reach/router": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@reach/router/-/router-1.3.4.tgz", + "integrity": "sha512-+mtn9wjlB9NN2CNnnC/BRYtwdKBfSyyasPYraNAyvaV1occr/5NnB4CVzjEZipNHwYebQwcndGUmpFzxAUoqSA==", "dev": true, "requires": { - "@babel/types": "^7.11.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "create-react-context": "0.3.0", + "invariant": "^2.2.3", + "prop-types": "^15.6.1", + "react-lifecycles-compat": "^3.0.4" } }, - "@babel/helper-annotate-as-pure": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", - "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", + "@storybook/addons": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.0.21.tgz", + "integrity": "sha512-yDttNLc3vXqBxwK795ykgzTC6MpvuXDQuF4LHSlHZQe6wsMu1m3fljnbYdafJWdx6cNZwUblU3KYcR11PqhkPg==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@storybook/api": "6.0.21", + "@storybook/channels": "6.0.21", + "@storybook/client-logger": "6.0.21", + "@storybook/core-events": "6.0.21", + "@storybook/router": "6.0.21", + "@storybook/theming": "6.0.21", + "core-js": "^3.0.1", + "global": "^4.3.2", + "regenerator-runtime": "^0.13.3" } }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", - "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", + "@storybook/api": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/api/-/api-6.0.21.tgz", + "integrity": "sha512-cRRGf/KGFwYiDouTouEcDdp45N1AbYnAfvLqYZ3KuUTGZ+CiU/PN/vavkp07DQeM4FIQO8TLhzHdsLFpLT7Lkw==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.10.4", - "@babel/types": "^7.10.4" + "@reach/router": "^1.3.3", + "@storybook/channels": "6.0.21", + "@storybook/client-logger": "6.0.21", + "@storybook/core-events": "6.0.21", + "@storybook/csf": "0.0.1", + "@storybook/router": "6.0.21", + "@storybook/semver": "^7.3.2", + "@storybook/theming": "6.0.21", + "@types/reach__router": "^1.3.5", + "core-js": "^3.0.1", + "fast-deep-equal": "^3.1.1", + "global": "^4.3.2", + "lodash": "^4.17.15", + "memoizerific": "^1.11.3", + "react": "^16.8.3", + "regenerator-runtime": "^0.13.3", + "store2": "^2.7.1", + "telejson": "^5.0.2", + "ts-dedent": "^1.1.1", + "util-deprecate": "^1.0.2" } }, - "@babel/helper-compilation-targets": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz", - "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", + "@storybook/channels": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.0.21.tgz", + "integrity": "sha512-G6gjcEotSwDmOlxSmOMgsO3VhQ42RLJK7kFp6D5eg0Q6S8vsypltdT8orxdu+6+AbcBrL+5Sla8lThzaCvXsVQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.10.4", - "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "levenary": "^1.1.1", - "semver": "^5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "core-js": "^3.0.1", + "ts-dedent": "^1.1.1", + "util-deprecate": "^1.0.2" } }, - "@babel/helper-create-class-features-plugin": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", - "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", + "@storybook/client-logger": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.0.21.tgz", + "integrity": "sha512-8aUEbhjXV+UMYQWukVYnp+kZafF+LD4Dm7eMo37IUZvt3VIjV1VvhxIDVJtqjk2vv0KZTepESFBkZQLmBzI9Zg==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.10.5", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4" + "core-js": "^3.0.1", + "global": "^4.3.2" } }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz", - "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==", + "@storybook/components": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-6.0.21.tgz", + "integrity": "sha512-r6btqFW/rcXIU5v231EifZfdh9O0fy7bJDXwwDf8zVUgLx8JRc0VnSs3nvK3Is9HF1wZ9vjx/7Lh4rTIDZAjgg==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-regex": "^7.10.4", - "regexpu-core": "^4.7.0" + "@storybook/client-logger": "6.0.21", + "@storybook/csf": "0.0.1", + "@storybook/theming": "6.0.21", + "@types/overlayscrollbars": "^1.9.0", + "@types/react-color": "^3.0.1", + "@types/react-syntax-highlighter": "11.0.4", + "core-js": "^3.0.1", + "fast-deep-equal": "^3.1.1", + "global": "^4.3.2", + "lodash": "^4.17.15", + "markdown-to-jsx": "^6.11.4", + "memoizerific": "^1.11.3", + "overlayscrollbars": "^1.10.2", + "polished": "^3.4.4", + "popper.js": "^1.14.7", + "react": "^16.8.3", + "react-color": "^2.17.0", + "react-dom": "^16.8.3", + "react-popper-tooltip": "^2.11.0", + "react-syntax-highlighter": "^12.2.1", + "react-textarea-autosize": "^8.1.1", + "ts-dedent": "^1.1.1" } }, - "@babel/helper-define-map": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", - "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", + "@storybook/core-events": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.0.21.tgz", + "integrity": "sha512-p84fbPcsAhnqDhp+HJ4P8+vI2BqJus4IRoVAemLAwuPjyPElrV9UvOa/RHy1BN8Z6jXwFA+FFzfGl2kPJ3WYcA==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/types": "^7.10.5", - "lodash": "^4.17.19" + "core-js": "^3.0.1" } }, - "@babel/helper-explode-assignable-expression": { - "version": "7.11.4", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.11.4.tgz", - "integrity": "sha512-ux9hm3zR4WV1Y3xXxXkdG/0gxF9nvI0YVmKVhvK9AfMoaQkemL3sJpXw+Xbz65azo8qJiEz2XVDUpK3KYhH3ZQ==", + "@storybook/router": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.0.21.tgz", + "integrity": "sha512-46SsKJfcd12lRrISnfrWhicJx8EylkgGDGohfH0n5p7inkkGOkKV8QFZoYPRKZueMXmUKpzJ0Z3HmVsLTCrCDw==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@reach/router": "^1.3.3", + "@types/reach__router": "^1.3.5", + "core-js": "^3.0.1", + "global": "^4.3.2", + "memoizerific": "^1.11.3", + "qs": "^6.6.0" } }, - "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "@storybook/semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" + "core-js": "^3.6.5", + "find-up": "^4.1.0" + }, + "dependencies": { + "core-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", + "dev": true + } } }, - "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "@storybook/theming": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.0.21.tgz", + "integrity": "sha512-n97DfB9kG6WrV1xBGDyeQibTrh8pBBCp3dSL3UTGH+KX3C2+4sm6QHlTgyekbi5FrbFEbnuZOKAS3YbLVONsRQ==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@emotion/core": "^10.0.20", + "@emotion/is-prop-valid": "^0.8.6", + "@emotion/styled": "^10.0.17", + "@storybook/client-logger": "6.0.21", + "core-js": "^3.0.1", + "deep-object-diff": "^1.1.0", + "emotion-theming": "^10.0.19", + "global": "^4.3.2", + "memoizerific": "^1.11.3", + "polished": "^3.4.4", + "resolve-from": "^5.0.0", + "ts-dedent": "^1.1.1" } }, - "@babel/helper-hoist-variables": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", - "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", + "@types/reach__router": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/reach__router/-/reach__router-1.3.5.tgz", + "integrity": "sha512-h0NbqXN/tJuBY/xggZSej1SKQEstbHO7J/omt1tYoFGmj3YXOodZKbbqD4mNDh7zvEGYd7YFrac1LTtAr3xsYQ==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@types/history": "*", + "@types/react": "*" } }, - "@babel/helper-member-expression-to-functions": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", - "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", - "dev": true, - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", - "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-module-transforms": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", - "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "@types/react-syntax-highlighter": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.4.tgz", + "integrity": "sha512-9GfTo3a0PHwQeTVoqs0g5bS28KkSY48pp5659wA+Dp4MqceDEa8EHBqrllJvvtyusszyJhViUEap0FDvlk/9Zg==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-simple-access": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/template": "^7.10.4", - "@babel/types": "^7.11.0", - "lodash": "^4.17.19" + "@types/react": "*" } }, - "@babel/helper-optimise-call-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", - "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "create-react-context": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz", + "integrity": "sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "gud": "^1.0.0", + "warning": "^4.0.3" } }, - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "@babel/helper-regex": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", - "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", - "dev": true, - "requires": { - "lodash": "^4.17.19" - } - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.11.4", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz", - "integrity": "sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-wrap-function": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-replace-supers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", - "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, - "@babel/helper-simple-access": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", - "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", - "dev": true, - "requires": { - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true }, - "@babel/helper-split-export-declaration": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", - "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", - "dev": true, - "requires": { - "@babel/types": "^7.11.0" - } + "highlight.js": { + "version": "9.15.10", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.10.tgz", + "integrity": "sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw==", + "dev": true }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", "dev": true }, - "@babel/helper-wrap-function": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz", - "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==", + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "has-symbols": "^1.0.1" } }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "has-symbols": "^1.0.1" } }, - "@babel/parser": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", - "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", "dev": true }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", - "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.10.4", - "@babel/plugin-syntax-async-generators": "^7.8.0" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", - "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz", - "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==", + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" + "p-locate": "^4.1.0" } }, - "@babel/plugin-proposal-json-strings": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", - "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==", + "lowlight": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.12.1.tgz", + "integrity": "sha512-OqaVxMGIESnawn+TU/QMV5BJLbUghUfjDWPAtFqDYDmDtr4FnB+op8xM+pR7nKlauHNUHXGt0VgWatFB8voS5w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.0" + "fault": "^1.0.2", + "highlight.js": "~9.15.0" } }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", - "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", + "markdown-to-jsx": { + "version": "6.11.4", + "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-6.11.4.tgz", + "integrity": "sha512-3lRCD5Sh+tfA52iGgfs/XZiw33f7fFX9Bn55aNnVNUd2GzLDkOWyKYYD8Yju2B1Vn+feiEdgJs8T6Tg0xNokPw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + "prop-types": "^15.6.2", + "unquote": "^1.1.0" } }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz", - "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==", + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "p-try": "^2.0.0" } }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz", - "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==", + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.10.4" + "p-limit": "^2.2.0" } }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz", - "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" - } + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz", - "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" - } + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz", - "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==", + "polished": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/polished/-/polished-3.6.6.tgz", + "integrity": "sha512-yiB2ims2DZPem0kCD6V0wnhcVGFEhNh0Iw0axNpKU+oSAgFt6yx6HxIT23Qg0WWvgS379cS35zT4AOyZZRzpQQ==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/runtime": "^7.9.2" } }, - "@babel/plugin-syntax-class-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", - "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } + "qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", + "dev": true }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "react-popper-tooltip": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/react-popper-tooltip/-/react-popper-tooltip-2.11.1.tgz", + "integrity": "sha512-04A2f24GhyyMicKvg/koIOQ5BzlrRbKiAgP6L+Pdj1MVX3yJ1NeZ8+EidndQsbejFT55oW1b++wg2Z8KlAyhfQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/runtime": "^7.9.2", + "react-popper": "^1.3.7" } }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "react-syntax-highlighter": { + "version": "12.2.1", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz", + "integrity": "sha512-CTsp0ZWijwKRYFg9xhkWD4DSpQqE4vb2NKVMdPAkomnILSmsNBHE0n5GuI5zB+PU3ySVvXvdt9jo+ViD9XibCA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/runtime": "^7.3.1", + "highlight.js": "~9.15.1", + "lowlight": "1.12.1", + "prismjs": "^1.8.4", + "refractor": "^2.4.1" } }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", - "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", + "react-textarea-autosize": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.2.0.tgz", + "integrity": "sha512-grajUlVbkx6VdtSxCgzloUIphIZF5bKr21OYMceWPKkniy7H0mRAT/AXPrRtObAe+zUePnNlBwUc4ivVjUGIjw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/runtime": "^7.10.2", + "use-composed-ref": "^1.0.0", + "use-latest": "^1.0.0" } }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz", - "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz", - "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.10.4" - } + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz", - "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==", + "telejson": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/telejson/-/telejson-5.0.2.tgz", + "integrity": "sha512-XCrDHGbinczsscs8LXFr9jDhvy37yBk9piB7FJrCfxE8oP66WDkolNMpaBkWYgQqB9dQGBGtTDzGQPedc9KJmw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@types/is-function": "^1.0.0", + "global": "^4.4.0", + "is-function": "^1.0.2", + "is-regex": "^1.1.1", + "is-symbol": "^1.0.3", + "isobject": "^4.0.0", + "lodash": "^4.17.19", + "memoizerific": "^1.11.3" } }, - "@babel/plugin-transform-block-scoping": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz", - "integrity": "sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } + "ts-dedent": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-1.1.1.tgz", + "integrity": "sha512-UGTRZu1evMw4uTPyYF66/KFd22XiU+jMaIuHrkIHQ2GivAXVlLV0v/vHrpOuTRf9BmpNHi/SO7Vd0rLu0y57jg==", + "dev": true }, - "@babel/plugin-transform-classes": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz", - "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==", + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-define-map": "^7.10.4", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4", - "globals": "^11.1.0" + "loose-envify": "^1.0.0" } + } + } + }, + "@stylelint/postcss-css-in-js": { + "version": "0.37.1", + "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.1.tgz", + "integrity": "sha512-UMf2Rni3JGKi3ZwYRGMYJ5ipOA5ENJSKMtYA/pE1ZLURwdh7B5+z2r73RmWvub+N0UuH1Lo+TGfCgYwPvqpXNw==", + "dev": true, + "requires": { + "@babel/core": ">=7.9.0" + } + }, + "@stylelint/postcss-markdown": { + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/@stylelint/postcss-markdown/-/postcss-markdown-0.36.1.tgz", + "integrity": "sha512-iDxMBWk9nB2BPi1VFQ+Dc5+XpvODBHw2n3tYpaBZuEAFQlbtF9If0Qh5LTTwSi/XwdbJ2jt+0dis3i8omyggpw==", + "dev": true, + "requires": { + "remark": "^12.0.0", + "unist-util-find-all-after": "^3.0.1" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true }, - "@babel/plugin-transform-computed-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz", - "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true }, - "@babel/plugin-transform-destructuring": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz", - "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==", + "markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "repeat-string": "^1.0.0" } }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz", - "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==", + "mdast-util-compact": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz", + "integrity": "sha512-7GlnT24gEwDrdAwEHrU4Vv5lLWrEer4KOkAiKT9nYstsTad7Oc1TwqT2zIMKRdZF7cTuaf+GA1E4Kv7jJh8mPA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "unist-util-visit": "^2.0.0" } }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz", - "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==", + "parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" } }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz", - "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==", + "remark": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/remark/-/remark-12.0.0.tgz", + "integrity": "sha512-oX4lMIS0csgk8AEbzY0h2jdR0ngiCHOpwwpxjmRa5TqAkeknY+tkhjRJGZqnCmvyuWh55/0SW5WY3R3nn3PH9A==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "remark-parse": "^8.0.0", + "remark-stringify": "^8.0.0", + "unified": "^9.0.0" } }, - "@babel/plugin-transform-for-of": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz", - "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==", + "remark-parse": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.2.tgz", + "integrity": "sha512-eMI6kMRjsAGpMXXBAywJwiwAse+KNpmt+BK55Oofy4KvBZEqUDj6mWbGLJZrujoPIPPxDXzn3T9baRlpsm2jnQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "ccount": "^1.0.0", + "collapse-white-space": "^1.0.2", + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "is-word-character": "^1.0.0", + "markdown-escapes": "^1.0.0", + "parse-entities": "^2.0.0", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "trim": "0.0.1", + "trim-trailing-lines": "^1.0.0", + "unherit": "^1.0.4", + "unist-util-remove-position": "^2.0.0", + "vfile-location": "^3.0.0", + "xtend": "^4.0.1" } }, - "@babel/plugin-transform-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz", - "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==", + "remark-stringify": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-8.1.0.tgz", + "integrity": "sha512-FSPZv1ds76oAZjurhhuV5qXSUSoz6QRPuwYK38S41sLHwg4oB7ejnmZshj7qwjgYLf93kdz6BOX9j5aidNE7rA==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "ccount": "^1.0.0", + "is-alphanumeric": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "longest-streak": "^2.0.1", + "markdown-escapes": "^1.0.0", + "markdown-table": "^2.0.0", + "mdast-util-compact": "^2.0.0", + "parse-entities": "^2.0.0", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "stringify-entities": "^3.0.0", + "unherit": "^1.0.4", + "xtend": "^4.0.1" } }, - "@babel/plugin-transform-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz", - "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==", + "stringify-entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.0.1.tgz", + "integrity": "sha512-Lsk3ISA2++eJYqBMPKcr/8eby1I6L0gP0NlxF8Zja6c05yr/yCYyb2c9PwXjd08Ib3If1vn1rbs1H5ZtVuOfvQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "character-entities-html4": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.2", + "is-hexadecimal": "^1.0.0" } }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz", - "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==", + "unified": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.0.0.tgz", + "integrity": "sha512-ssFo33gljU3PdlWLjNp15Inqb77d6JnJSfyplGJPT/a+fNRNyCBeveBAYJdO5khKdF6WVHa/yYCC7Xl6BDwZUQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^2.0.0", + "trough": "^1.0.0", + "vfile": "^4.0.0" } }, - "@babel/plugin-transform-modules-amd": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", - "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.10.5", - "@babel/helper-plugin-utils": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" - } + "unist-util-is": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", + "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==", + "dev": true }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz", - "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==", + "unist-util-remove-position": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz", + "integrity": "sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-simple-access": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" + "unist-util-visit": "^2.0.0" } }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", - "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", + "unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.10.4", - "@babel/helper-module-transforms": "^7.10.5", - "@babel/helper-plugin-utils": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" + "@types/unist": "^2.0.2" } }, - "@babel/plugin-transform-modules-umd": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", - "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", + "unist-util-visit": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.2.tgz", + "integrity": "sha512-HoHNhGnKj6y+Sq+7ASo2zpVdfdRifhTgX2KTU3B/sO/TTlZchp7E3S4vjRzDJ7L60KmrCPsQkVK3lEF3cz36XQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" } }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", - "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", + "unist-util-visit-parents": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.0.2.tgz", + "integrity": "sha512-yJEfuZtzFpQmg1OSCyS9M5NJRrln/9FbYosH3iW0MG402QbdbaB8ZESwUv9RO6nRfLAKvWcMxCwdLWOov36x/g==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4" + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" } }, - "@babel/plugin-transform-new-target": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", - "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", + "vfile": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.1.1.tgz", + "integrity": "sha512-lRjkpyDGjVlBA7cDQhQ+gNcvB1BGaTHYuSOcY3S7OhDmBtnzX95FhtZZDecSTDm6aajFymyve6S5DN4ZHGezdQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "replace-ext": "1.0.0", + "unist-util-stringify-position": "^2.0.0", + "vfile-message": "^2.0.0" } }, - "@babel/plugin-transform-object-super": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", - "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4" - } + "vfile-location": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.0.1.tgz", + "integrity": "sha512-yYBO06eeN/Ki6Kh1QAkgzYpWT1d3Qln+ZCtSbJqFExPl1S3y2qqotJQXoh6qEvl/jDlgpUJolBn3PItVnnZRqQ==", + "dev": true }, - "@babel/plugin-transform-parameters": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", - "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", + "vfile-message": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", + "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" } + } + } + }, + "@svgr/babel-plugin-add-jsx-attribute": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.0.1.tgz", + "integrity": "sha512-av76JbSudaN2CUOGuKQp5BVqLFidtojg4ApRTg1PBOVsskXK2ORwKnBYhIu0JLA6ynmuNDprlHNCD6IwLiYidw==", + "dev": true + }, + "@svgr/babel-plugin-remove-jsx-attribute": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.0.1.tgz", + "integrity": "sha512-oXyByaDQEK4Ut/eC75698MDKnaadbWmp/LS2w22cZAaoObCkkiwYYgZTZ+bvb3moo//AxvKkBtNrlz6+xBb9ZQ==", + "dev": true + }, + "@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", + "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==", + "dev": true + }, + "@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", + "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==", + "dev": true + }, + "@svgr/babel-plugin-svg-dynamic-title": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.0.1.tgz", + "integrity": "sha512-IbFiDBvq5WpANPndjEom1Y9k1pHCNfJs87jCN1Lt8NEA7yrNVPSoAjBVmmfi0aVBERfp8IT/lgjn2a/S85lXGg==", + "dev": true + }, + "@svgr/babel-plugin-svg-em-dimensions": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.0.1.tgz", + "integrity": "sha512-7kL9LtqCm1ca+zAbBsrD4ME3EQeVcRxkdrf2GsbKPgkzWJ+399vS4VqCP462+WvFRbG13jSwpNCrvhekdyvXsA==", + "dev": true + }, + "@svgr/babel-plugin-transform-react-native-svg": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.0.1.tgz", + "integrity": "sha512-ITG1jJ0zHQ4yft6ISQqlMW4fHIzsrSB/FmrMxAcJtkTjh9M2/9M8wfKxQya9NnTfZ5WMSlQjXMQNZmGQsuxRrw==", + "dev": true + }, + "@svgr/babel-plugin-transform-svg-component": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.2.0.tgz", + "integrity": "sha512-t4dq0cNr7c8cuUu1Cwehai/0iXO3dV5876r2QRaLdgQF3C6XOK2vdTvNOwcJ3uRa92revSC3kGL8v8WgJrecRg==", + "dev": true + }, + "@svgr/babel-preset": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.2.0.tgz", + "integrity": "sha512-sr7486h+SddMU1VgFrajXx/Ws0a1QPzX4wUBM1LgG2PHeZpnm+fQs2MXQNdnfoXRwo7C5mH2I4QDiRVR/49BEg==", + "dev": true, + "requires": { + "@svgr/babel-plugin-add-jsx-attribute": "^5.0.1", + "@svgr/babel-plugin-remove-jsx-attribute": "^5.0.1", + "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", + "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", + "@svgr/babel-plugin-svg-dynamic-title": "^5.0.1", + "@svgr/babel-plugin-svg-em-dimensions": "^5.0.1", + "@svgr/babel-plugin-transform-react-native-svg": "^5.0.1", + "@svgr/babel-plugin-transform-svg-component": "^5.2.0" + } + }, + "@svgr/core": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.2.0.tgz", + "integrity": "sha512-vuODnJ0owj/oFi2bzskuSEk6TGuYoMV9hmvBhGuE1QktzMAAjOr0LnvUN5u2eGB6ilGdI7yqUKrZtQ0Tw44mrA==", + "dev": true, + "requires": { + "@svgr/plugin-jsx": "^5.2.0", + "camelcase": "^5.3.1", + "cosmiconfig": "^6.0.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true }, - "@babel/plugin-transform-property-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", - "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" } }, - "@babel/plugin-transform-regenerator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", - "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, "requires": { - "regenerator-transform": "^0.14.2" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" } }, - "@babel/plugin-transform-reserved-words": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", - "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + } + } + }, + "@svgr/hast-util-to-babel-ast": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.0.1.tgz", + "integrity": "sha512-G7UHNPNhLyDK5p6RJvSh4TRpHszTxG8jPp5lAxC6Ez6O6rj1plEAjrCDdYj50mvilUuT9IKjqn87F8+agpKaSw==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@svgr/plugin-jsx": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.2.0.tgz", + "integrity": "sha512-z1HWitE5sCNgaXqBGrmCnnnvR/BRTq9B/lsgZ+T8OWABzZHhezqjjDUvkyyyBb3Y+0xExWg5aTh2jxqk7GR9tg==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@svgr/babel-preset": "^5.2.0", + "@svgr/hast-util-to-babel-ast": "^5.0.1", + "svg-parser": "^2.0.2" + } + }, + "@svgr/plugin-svgo": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.2.0.tgz", + "integrity": "sha512-cyqWx026uO3heGG/55j5zfJLtS5sl0dWYawN1JotOqpJDyyR7rraTsnydpwwsOoz0YpESjVjAkXOAfd41lBY9Q==", + "dev": true, + "requires": { + "cosmiconfig": "^6.0.0", + "merge-deep": "^3.0.2", + "svgo": "^1.2.2" + }, + "dependencies": { + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" } }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", - "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", + "css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" } }, - "@babel/plugin-transform-spread": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz", - "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==", + "css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0" + "mdn-data": "2.0.4", + "source-map": "^0.6.1" } }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", - "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", + "css-what": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz", + "integrity": "sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==", + "dev": true + }, + "csso": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.2.tgz", + "integrity": "sha512-kS7/oeNVXkHWxby5tHVxlhjizRCSv8QdU7hB2FpdAibDU8FjTAolhNjKNTiLzXtUrKT6HwClE81yXwEk1309wg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-regex": "^7.10.4" + "css-tree": "1.0.0-alpha.37" } }, - "@babel/plugin-transform-template-literals": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", - "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "object-keys": "^1.0.12" } }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", - "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "dom-serializer": "0", + "domelementtype": "1" } }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", - "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/preset-env": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz", - "integrity": "sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.11.0", - "@babel/helper-compilation-targets": "^7.10.4", - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-proposal-async-generator-functions": "^7.10.4", - "@babel/plugin-proposal-class-properties": "^7.10.4", - "@babel/plugin-proposal-dynamic-import": "^7.10.4", - "@babel/plugin-proposal-export-namespace-from": "^7.10.4", - "@babel/plugin-proposal-json-strings": "^7.10.4", - "@babel/plugin-proposal-logical-assignment-operators": "^7.11.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", - "@babel/plugin-proposal-numeric-separator": "^7.10.4", - "@babel/plugin-proposal-object-rest-spread": "^7.11.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", - "@babel/plugin-proposal-optional-chaining": "^7.11.0", - "@babel/plugin-proposal-private-methods": "^7.10.4", - "@babel/plugin-proposal-unicode-property-regex": "^7.10.4", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.10.4", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.10.4", - "@babel/plugin-transform-arrow-functions": "^7.10.4", - "@babel/plugin-transform-async-to-generator": "^7.10.4", - "@babel/plugin-transform-block-scoped-functions": "^7.10.4", - "@babel/plugin-transform-block-scoping": "^7.10.4", - "@babel/plugin-transform-classes": "^7.10.4", - "@babel/plugin-transform-computed-properties": "^7.10.4", - "@babel/plugin-transform-destructuring": "^7.10.4", - "@babel/plugin-transform-dotall-regex": "^7.10.4", - "@babel/plugin-transform-duplicate-keys": "^7.10.4", - "@babel/plugin-transform-exponentiation-operator": "^7.10.4", - "@babel/plugin-transform-for-of": "^7.10.4", - "@babel/plugin-transform-function-name": "^7.10.4", - "@babel/plugin-transform-literals": "^7.10.4", - "@babel/plugin-transform-member-expression-literals": "^7.10.4", - "@babel/plugin-transform-modules-amd": "^7.10.4", - "@babel/plugin-transform-modules-commonjs": "^7.10.4", - "@babel/plugin-transform-modules-systemjs": "^7.10.4", - "@babel/plugin-transform-modules-umd": "^7.10.4", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4", - "@babel/plugin-transform-new-target": "^7.10.4", - "@babel/plugin-transform-object-super": "^7.10.4", - "@babel/plugin-transform-parameters": "^7.10.4", - "@babel/plugin-transform-property-literals": "^7.10.4", - "@babel/plugin-transform-regenerator": "^7.10.4", - "@babel/plugin-transform-reserved-words": "^7.10.4", - "@babel/plugin-transform-shorthand-properties": "^7.10.4", - "@babel/plugin-transform-spread": "^7.11.0", - "@babel/plugin-transform-sticky-regex": "^7.10.4", - "@babel/plugin-transform-template-literals": "^7.10.4", - "@babel/plugin-transform-typeof-symbol": "^7.10.4", - "@babel/plugin-transform-unicode-escapes": "^7.10.4", - "@babel/plugin-transform-unicode-regex": "^7.10.4", - "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.11.5", - "browserslist": "^4.12.0", - "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", - "semver": "^5.5.0" + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" }, "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true } } }, - "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" } }, - "@babel/traverse": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", - "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.5", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.11.5", - "@babel/types": "^7.11.5", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" + "has": "^1.0.3" } }, - "@babel/types": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", - "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" + "has-symbols": "^1.0.1" } }, - "@emotion/is-prop-valid": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", - "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "dev": true + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", "dev": true, "requires": { - "@emotion/memoize": "0.7.4" + "boolbase": "~1.0.0" } }, - "@emotion/memoize": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", "dev": true }, - "@reach/router": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@reach/router/-/router-1.3.4.tgz", - "integrity": "sha512-+mtn9wjlB9NN2CNnnC/BRYtwdKBfSyyasPYraNAyvaV1occr/5NnB4CVzjEZipNHwYebQwcndGUmpFzxAUoqSA==", + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", "dev": true, "requires": { - "create-react-context": "0.3.0", - "invariant": "^2.2.3", - "prop-types": "^15.6.1", - "react-lifecycles-compat": "^3.0.4" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" } }, - "@storybook/addons": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.0.21.tgz", - "integrity": "sha512-yDttNLc3vXqBxwK795ykgzTC6MpvuXDQuF4LHSlHZQe6wsMu1m3fljnbYdafJWdx6cNZwUblU3KYcR11PqhkPg==", + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, "requires": { - "@storybook/api": "6.0.21", - "@storybook/channels": "6.0.21", - "@storybook/client-logger": "6.0.21", - "@storybook/core-events": "6.0.21", - "@storybook/router": "6.0.21", - "@storybook/theming": "6.0.21", - "core-js": "^3.0.1", - "global": "^4.3.2", - "regenerator-runtime": "^0.13.3" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" } }, - "@storybook/api": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/api/-/api-6.0.21.tgz", - "integrity": "sha512-cRRGf/KGFwYiDouTouEcDdp45N1AbYnAfvLqYZ3KuUTGZ+CiU/PN/vavkp07DQeM4FIQO8TLhzHdsLFpLT7Lkw==", + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", "dev": true, "requires": { - "@reach/router": "^1.3.3", - "@storybook/channels": "6.0.21", - "@storybook/client-logger": "6.0.21", - "@storybook/core-events": "6.0.21", - "@storybook/csf": "0.0.1", - "@storybook/router": "6.0.21", - "@storybook/semver": "^7.3.2", - "@storybook/theming": "6.0.21", - "@types/reach__router": "^1.3.5", - "core-js": "^3.0.1", - "fast-deep-equal": "^3.1.1", - "global": "^4.3.2", - "lodash": "^4.17.15", - "memoizerific": "^1.11.3", - "react": "^16.8.3", - "regenerator-runtime": "^0.13.3", - "store2": "^2.7.1", - "telejson": "^5.0.2", - "ts-dedent": "^1.1.1", - "util-deprecate": "^1.0.2" + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" } }, - "@storybook/channels": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.0.21.tgz", - "integrity": "sha512-G6gjcEotSwDmOlxSmOMgsO3VhQ42RLJK7kFp6D5eg0Q6S8vsypltdT8orxdu+6+AbcBrL+5Sla8lThzaCvXsVQ==", + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", "dev": true, "requires": { - "core-js": "^3.0.1", - "ts-dedent": "^1.1.1", - "util-deprecate": "^1.0.2" + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" } }, - "@storybook/client-logger": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.0.21.tgz", - "integrity": "sha512-8aUEbhjXV+UMYQWukVYnp+kZafF+LD4Dm7eMo37IUZvt3VIjV1VvhxIDVJtqjk2vv0KZTepESFBkZQLmBzI9Zg==", + "svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", "dev": true, "requires": { - "core-js": "^3.0.1", - "global": "^4.3.2" + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } } + } + } + }, + "@svgr/webpack": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.2.0.tgz", + "integrity": "sha512-y9tMjTtqrvC998aCOgsEP8pb+bdg2RR1v8i+sjT31m4Xb8HGd905K7/GdSwEMM2nlTFbxSUQPZRRvfaJwAvghA==", + "dev": true, + "requires": { + "@babel/core": "^7.4.5", + "@babel/plugin-transform-react-constant-elements": "^7.0.0", + "@babel/preset-env": "^7.4.5", + "@babel/preset-react": "^7.0.0", + "@svgr/core": "^5.2.0", + "@svgr/plugin-jsx": "^5.2.0", + "@svgr/plugin-svgo": "^5.2.0", + "loader-utils": "^1.2.3" + }, + "dependencies": { + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true }, - "@storybook/core-events": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.0.21.tgz", - "integrity": "sha512-p84fbPcsAhnqDhp+HJ4P8+vI2BqJus4IRoVAemLAwuPjyPElrV9UvOa/RHy1BN8Z6jXwFA+FFzfGl2kPJ3WYcA==", + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, "requires": { - "core-js": "^3.0.1" + "minimist": "^1.2.0" } }, - "@storybook/router": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.0.21.tgz", - "integrity": "sha512-46SsKJfcd12lRrISnfrWhicJx8EylkgGDGohfH0n5p7inkkGOkKV8QFZoYPRKZueMXmUKpzJ0Z3HmVsLTCrCDw==", + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", "dev": true, "requires": { - "@reach/router": "^1.3.3", - "@types/reach__router": "^1.3.5", - "core-js": "^3.0.1", - "global": "^4.3.2", - "memoizerific": "^1.11.3", - "qs": "^6.6.0" + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" } + } + } + }, + "@szmarczak/http-timer": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", + "dev": true, + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "@tannin/compile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@tannin/compile/-/compile-1.1.0.tgz", + "integrity": "sha512-n8m9eNDfoNZoxdvWiTfW/hSPhehzLJ3zW7f8E7oT6mCROoMNWCB4TYtv041+2FMAxweiE0j7i1jubQU4MEC/Gg==", + "requires": { + "@tannin/evaluate": "^1.2.0", + "@tannin/postfix": "^1.1.0" + } + }, + "@tannin/evaluate": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@tannin/evaluate/-/evaluate-1.2.0.tgz", + "integrity": "sha512-3ioXvNowbO/wSrxsDG5DKIMxC81P0QrQTYai8zFNY+umuoHWRPbQ/TuuDEOju9E+jQDXmj6yI5GyejNuh8I+eg==" + }, + "@tannin/plural-forms": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@tannin/plural-forms/-/plural-forms-1.1.0.tgz", + "integrity": "sha512-xl9R2mDZO/qiHam1AgMnAES6IKIg7OBhcXqy6eDsRCdXuxAFPcjrej9HMjyCLE0DJ/8cHf0i5OQTstuBRhpbHw==", + "requires": { + "@tannin/compile": "^1.1.0" + } + }, + "@tannin/postfix": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@tannin/postfix/-/postfix-1.1.0.tgz", + "integrity": "sha512-oocsqY7g0cR+Gur5jRQLSrX2OtpMLMse1I10JQBm8CdGMrDkh1Mg2gjsiquMHRtBs4Qwu5wgEp5GgIYHk4SNPw==" + }, + "@testing-library/dom": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.2.1.tgz", + "integrity": "sha512-xIGoHlQ2ZiEL1dJIFKNmLDypzYF+4OJTTASRctl/aoIDaS5y/pRVHRigoqvPUV11mdJoR71IIgi/6UviMgyz4g==", + "dev": true, + "requires": { + "@babel/runtime": "^7.9.2", + "@types/testing-library__dom": "^7.0.0", + "aria-query": "^4.0.2", + "dom-accessibility-api": "^0.4.2", + "pretty-format": "^25.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true }, - "@storybook/semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "core-js": "^3.6.5", - "find-up": "^4.1.0" - }, - "dependencies": { - "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", - "dev": true - } + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, - "@storybook/theming": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.0.21.tgz", - "integrity": "sha512-n97DfB9kG6WrV1xBGDyeQibTrh8pBBCp3dSL3UTGH+KX3C2+4sm6QHlTgyekbi5FrbFEbnuZOKAS3YbLVONsRQ==", + "aria-query": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.0.2.tgz", + "integrity": "sha512-S1G1V790fTaigUSM/Gd0NngzEfiMy9uTUfMyHhKhVyy4cH5O/eTuR01ydhGL0z4Za1PXFTRGH3qL8VhUQuEO5w==", "dev": true, "requires": { - "@emotion/core": "^10.0.20", - "@emotion/is-prop-valid": "^0.8.6", - "@emotion/styled": "^10.0.17", - "@storybook/client-logger": "6.0.21", - "core-js": "^3.0.1", - "deep-object-diff": "^1.1.0", - "emotion-theming": "^10.0.19", - "global": "^4.3.2", - "memoizerific": "^1.11.3", - "polished": "^3.4.4", - "resolve-from": "^5.0.0", - "ts-dedent": "^1.1.1" + "@babel/runtime": "^7.7.4", + "@babel/runtime-corejs3": "^7.7.4" } }, - "@svgr/babel-plugin-add-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==", - "dev": true - }, - "@svgr/babel-plugin-remove-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==", - "dev": true + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } }, - "@svgr/babel-plugin-svg-dynamic-title": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", - "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "@svgr/babel-plugin-svg-em-dimensions": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", - "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==", - "dev": true + "pretty-format": { + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.3.0.tgz", + "integrity": "sha512-wToHwF8bkQknIcFkBqNfKu4+UZqnrLn/Vr+wwKQwwvPzkBfDDKp/qIabFqdgtoi5PEnM8LFByVsOrHoa3SpTVA==", + "dev": true, + "requires": { + "@jest/types": "^25.3.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } }, - "@svgr/babel-plugin-transform-react-native-svg": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", - "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==", + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, - "@svgr/babel-plugin-transform-svg-component": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.4.0.tgz", - "integrity": "sha512-zLl4Fl3NvKxxjWNkqEcpdSOpQ3LGVH2BNFQ6vjaK6sFo2IrSznrhURIPI0HAphKiiIwNYjAfE0TNoQDSZv0U9A==", - "dev": true - }, - "@svgr/babel-preset": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.4.0.tgz", - "integrity": "sha512-Gyx7cCxua04DBtyILTYdQxeO/pwfTBev6+eXTbVbxe4HTGhOUW6yo7PSbG2p6eJMl44j6XSequ0ZDP7bl0nu9A==", + "regenerator-runtime": { + "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + } + } + }, + "@testing-library/react": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-10.0.2.tgz", + "integrity": "sha512-YT6Mw0oJz7R6vlEkmo1FlUD+K15FeXApOB5Ffm9zooFVnrwkt00w18dUJFMOh1yRp9wTdVRonbor7o4PIpFCmA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.9.2", + "@testing-library/dom": "^7.1.0", + "@types/testing-library__react": "^10.0.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + } + } + }, + "@types/anymatch": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", + "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", + "dev": true + }, + "@types/babel-types": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.7.tgz", + "integrity": "sha512-dBtBbrc+qTHy1WdfHYjBwRln4+LWqASWakLHsWHR2NWHIFkv4W3O070IGoGLEBrJBvct3r0L1BUPuvURi7kYUQ==", + "dev": true + }, + "@types/babel__core": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.7.tgz", + "integrity": "sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", + "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", + "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.11.tgz", + "integrity": "sha512-ddHK5icION5U6q11+tV2f9Mo6CZVuT8GJKld2q9LqHSZbvLbH34Kcu2yFGckZut453+eQU6btIA3RihmnRgI+Q==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/babylon": { + "version": "6.16.5", + "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.5.tgz", + "integrity": "sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==", + "dev": true, + "requires": { + "@types/babel-types": "*" + } + }, + "@types/braces": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.0.tgz", + "integrity": "sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==", + "dev": true + }, + "@types/cacheable-request": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "dev": true, + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "@types/classnames": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.10.tgz", + "integrity": "sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ==", + "dev": true + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + }, + "@types/eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-hqzmggoxkOubpgTdcOltkfc5N8IftRJqU70d1jbOISjjZVPvjcr+CLi2CI70hx1SUIRkLgpglTy9w28nGe2Hsw==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/estree": { + "version": "0.0.44", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.44.tgz", + "integrity": "sha512-iaIVzr+w2ZJ5HkidlZ3EJM8VTZb2MJLCjw3V+505yVts0gRC4UMvjw0d1HPtGqI/HQC/KdsYtayfzl+AXY2R8g==", + "dev": true + }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@types/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-pYHWiDR+EOUN18F9byiAoQNUMZ0=", + "dev": true + }, + "@types/graceful-fs": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz", + "integrity": "sha512-AiHRaEB50LQg0pZmm659vNBb9f4SJ0qrAnteuzhSeAUcJKxoYgEnprg/83kppCnc2zvtCKbdZry1a5pVY3lOTQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/hammerjs": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.36.tgz", + "integrity": "sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ==" + }, + "@types/history": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.4.tgz", + "integrity": "sha512-+o2igcuZA3xtOoFH56s+MCZVidwlJNcJID57DSCyawS2i910yG9vkwehCjJNZ6ImhCR5S9DbvIJKyYHcMyOfMw==", + "dev": true + }, + "@types/html-minifier-terser": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz", + "integrity": "sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA==", + "dev": true + }, + "@types/http-cache-semantics": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", + "dev": true + }, + "@types/is-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/is-function/-/is-function-1.0.0.tgz", + "integrity": "sha512-iTs9HReBu7evG77Q4EC8hZnqRt57irBDkK9nvmHroiOIVwYMQc4IvYvdRgwKfYepunIY7Oh/dBuuld+Gj9uo6w==", + "dev": true + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", + "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==" + }, + "@types/istanbul-lib-report": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz", + "integrity": "sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==", + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", + "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", + "requires": { + "@types/istanbul-lib-coverage": "*", + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "23.3.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", + "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", + "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", + "dev": true + }, + "@types/keyv": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/lodash": { + "version": "4.14.149", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", + "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==", + "dev": true + }, + "@types/markdown-to-jsx": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/@types/markdown-to-jsx/-/markdown-to-jsx-6.11.2.tgz", + "integrity": "sha512-ESuCu8Bk7jpTZ3YPdMW1+6wUj13F5N15vXfc7BuUAN0eCp0lrvVL9nzOTzoqvbRzXMciuqXr1KrHt3xQAhfwPA==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/micromatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.1.tgz", + "integrity": "sha512-my6fLBvpY70KattTNzYOK6KU1oR1+UCz9ug/JbcF5UrEmeCt9P7DV2t7L8+t18mMPINqGQCE4O8PLOPbI84gxw==", + "dev": true, + "requires": { + "@types/braces": "*" + } + }, + "@types/mime-types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.0.tgz", + "integrity": "sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM=", + "dev": true + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", + "dev": true + }, + "@types/node": { + "version": "12.7.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.11.tgz", + "integrity": "sha512-Otxmr2rrZLKRYIybtdG/sgeO+tHY20GxeDjcGmUnmmlCWyEnv2a2x1ZXBo3BTec4OiTXMQCiazB8NMBf0iRlFw==", + "dev": true + }, + "@types/node-fetch": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz", + "integrity": "sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + }, + "dependencies": { + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "requires": { - "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", - "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", - "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", - "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", - "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", - "@svgr/babel-plugin-transform-svg-component": "^5.4.0" + "delayed-stream": "~1.0.0" } }, - "@svgr/core": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.4.0.tgz", - "integrity": "sha512-hWGm1DCCvd4IEn7VgDUHYiC597lUYhFau2lwJBYpQWDirYLkX4OsXu9IslPgJ9UpP7wsw3n2Ffv9sW7SXJVfqQ==", + "form-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", "dev": true, "requires": { - "@svgr/plugin-jsx": "^5.4.0", - "camelcase": "^6.0.0", - "cosmiconfig": "^6.0.0" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" } + } + } + }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, + "@types/npm-package-arg": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/npm-package-arg/-/npm-package-arg-6.1.0.tgz", + "integrity": "sha512-vbt5fb0y1svMhu++1lwtKmZL76d0uPChFlw7kEzyUmTwfmpHRcFb8i0R8ElT69q/L+QLgK2hgECivIAvaEDwag==", + "dev": true + }, + "@types/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@types/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-4QQmOF5KlwfxJ5IGXFIudkeLCdMABz03RcUXu+LCb24zmln8QW6aDjuGl4d4XPVLf2j+FnjelHTP7dvceAFbhA==", + "dev": true + }, + "@types/overlayscrollbars": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@types/overlayscrollbars/-/overlayscrollbars-1.12.0.tgz", + "integrity": "sha512-h/pScHNKi4mb+TrJGDon8Yb06ujFG0mSg12wIO0sWMUF3dQIe2ExRRdNRviaNt9IjxIiOfnRr7FsQAdHwK4sMg==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + }, + "@types/prettier": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.0.tgz", + "integrity": "sha512-gDE8JJEygpay7IjA/u3JiIURvwZW08f0cZSZLAzFoX/ZmeqvS0Sqv+97aKuHpNsalAMMhwPe+iAS6fQbfmbt7A==", + "dev": true + }, + "@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" + }, + "@types/q": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", + "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", + "dev": true + }, + "@types/qs": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.1.tgz", + "integrity": "sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw==", + "dev": true + }, + "@types/reach__router": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@types/reach__router/-/reach__router-1.2.6.tgz", + "integrity": "sha512-Oh5DAVr/L2svBvubw6QEFpXGu295Y406BPs4i9t1n2pp7M+q3pmCmhzb9oZV5wncR41KCD3NHl1Yhi7uKnTPsA==", + "dev": true, + "requires": { + "@types/history": "*", + "@types/react": "*" + } + }, + "@types/react": { + "version": "16.9.49", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.49.tgz", + "integrity": "sha512-DtLFjSj0OYAdVLBbyjhuV9CdGVHCkHn2R+xr3XkBvK2rS1Y1tkc14XSGjYgm5Fjjr90AxH9tiSzc1pCFMGO06g==", + "requires": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + }, + "dependencies": { + "csstype": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.3.tgz", + "integrity": "sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==" + } + } + }, + "@types/react-color": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.1.tgz", + "integrity": "sha512-J6mYm43Sid9y+OjZ7NDfJ2VVkeeuTPNVImNFITgQNXodHteKfl/t/5pAR5Z9buodZ2tCctsZjgiMlQOpfntakw==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/react-dom": { + "version": "16.9.8", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", + "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==", + "requires": { + "@types/react": "*" + } + }, + "@types/react-native": { + "version": "0.57.65", + "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.57.65.tgz", + "integrity": "sha512-7P5ulTb+/cnwbABWaAjzKmSYkRWeK7UCTfUwHhDpnwxdiL2X/KbdN1sPgo0B2E4zxfYE3MEoHv7FhB8Acfvf8A==", + "requires": { + "@types/prop-types": "*", + "@types/react": "*" + } + }, + "@types/react-syntax-highlighter": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.2.tgz", + "integrity": "sha512-iMNcixH8330f2dq0RY+VOXCP8JFehgmOhLOtnO85Ty+qu0fHXJNEqWx5VuFv8v0aEq0U/N9d/k1yvA+c6PEmPw==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/react-textarea-autosize": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@types/react-textarea-autosize/-/react-textarea-autosize-4.3.5.tgz", + "integrity": "sha512-PiDL83kPMTolyZAWW3lyzO6ktooTb9tFTntVy7CA83/qFLWKLJ5bLeRboy6J6j3b1e8h2Eec6gBTEOOJRjV14A==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/requestidlecallback": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@types/requestidlecallback/-/requestidlecallback-0.3.1.tgz", + "integrity": "sha512-BnnRkgWYijCIndUn+LgoqKHX/hNpJC5G03B9y7mZya/C2gUQTSn75fEj3ZP1/Rl2E6EYeXh2/7/8UNEZ4X7HuQ==", + "dev": true + }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/semver": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.2.0.tgz", + "integrity": "sha512-TbB0A8ACUWZt3Y6bQPstW9QNbhNeebdgLX4T/ZfkrswAfUzRiXrgd9seol+X379Wa589Pu4UEx9Uok0D4RjRCQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "dev": true + }, + "@types/sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@types/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-hkgzYF+qnIl8uTO8rmUSVSfQ8BIfMXC4yJAF4n8BE758YsKBZvFC4NumnAegj7KmylP0liEZNpb9RRGFMbFejA==", + "dev": true + }, + "@types/stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" + }, + "@types/tapable": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.5.tgz", + "integrity": "sha512-/gG2M/Imw7cQFp8PGvz/SwocNrmKFjFsm5Pb8HdbHkZ1K8pmuPzOX4VeVoiEecFCVf4CsN1r3/BRvx+6sNqwtQ==", + "dev": true + }, + "@types/testing-library__dom": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@types/testing-library__dom/-/testing-library__dom-7.0.1.tgz", + "integrity": "sha512-WokGRksRJb3Dla6h02/0/NNHTkjsj4S8aJZiwMj/5/UL8VZ1iCe3H8SHzfpmBeH8Vp4SPRT8iC2o9kYULFhDIw==", + "dev": true, + "requires": { + "pretty-format": "^25.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true }, - "@svgr/hast-util-to-babel-ast": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.4.0.tgz", - "integrity": "sha512-+U0TZZpPsP2V1WvVhqAOSTk+N+CjYHdZx+x9UBa1eeeZDXwH8pt0CrQf2+SvRl/h2CAPRFkm+Ey96+jKP8Bsgg==", - "dev": true, - "requires": { - "@babel/types": "^7.9.5" - } - }, - "@svgr/plugin-jsx": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.4.0.tgz", - "integrity": "sha512-SGzO4JZQ2HvGRKDzRga9YFSqOqaNrgLlQVaGvpZ2Iht2gwRp/tq+18Pvv9kS9ZqOMYgyix2LLxZMY1LOe9NPqw==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@svgr/babel-preset": "^5.4.0", - "@svgr/hast-util-to-babel-ast": "^5.4.0", - "svg-parser": "^2.0.2" - } - }, - "@svgr/plugin-svgo": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.4.0.tgz", - "integrity": "sha512-3Cgv3aYi1l6SHyzArV9C36yo4kgwVdF3zPQUC6/aCDUeXAofDYwE5kk3e3oT5ZO2a0N3lB+lLGvipBG6lnG8EA==", - "dev": true, - "requires": { - "cosmiconfig": "^6.0.0", - "merge-deep": "^3.0.2", - "svgo": "^1.2.2" - } - }, - "@svgr/webpack": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.4.0.tgz", - "integrity": "sha512-LjepnS/BSAvelnOnnzr6Gg0GcpLmnZ9ThGFK5WJtm1xOqdBE/1IACZU7MMdVzjyUkfFqGz87eRE4hFaSLiUwYg==", - "dev": true, - "requires": { - "@babel/core": "^7.9.0", - "@babel/plugin-transform-react-constant-elements": "^7.9.0", - "@babel/preset-env": "^7.9.5", - "@babel/preset-react": "^7.9.4", - "@svgr/core": "^5.4.0", - "@svgr/plugin-jsx": "^5.4.0", - "@svgr/plugin-svgo": "^5.4.0", - "loader-utils": "^2.0.0" - } - }, - "@types/reach__router": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/reach__router/-/reach__router-1.3.5.tgz", - "integrity": "sha512-h0NbqXN/tJuBY/xggZSej1SKQEstbHO7J/omt1tYoFGmj3YXOodZKbbqD4mNDh7zvEGYd7YFrac1LTtAr3xsYQ==", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "@types/history": "*", - "@types/react": "*" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, - "@webassemblyjs/ast": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", - "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0" + "color-name": "~1.1.4" } }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", - "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", - "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", - "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "@webassemblyjs/helper-code-frame": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", - "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", + "pretty-format": { + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.3.0.tgz", + "integrity": "sha512-wToHwF8bkQknIcFkBqNfKu4+UZqnrLn/Vr+wwKQwwvPzkBfDDKp/qIabFqdgtoi5PEnM8LFByVsOrHoa3SpTVA==", "dev": true, "requires": { - "@webassemblyjs/wast-printer": "1.9.0" + "@jest/types": "^25.3.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" } }, - "@webassemblyjs/helper-fsm": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", - "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true - }, - "@webassemblyjs/helper-module-context": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", - "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", - "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + } + } + }, + "@types/testing-library__react": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/testing-library__react/-/testing-library__react-10.0.1.tgz", + "integrity": "sha512-RbDwmActAckbujLZeVO/daSfdL1pnjVqas25UueOkAY5r7vriavWf0Zqg7ghXMHa8ycD/kLkv8QOj31LmSYwww==", + "dev": true, + "requires": { + "@types/react-dom": "*", + "@types/testing-library__dom": "*", + "pretty-format": "^25.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", - "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", - "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "@xtuc/ieee754": "^1.2.0" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, - "@webassemblyjs/leb128": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", - "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "@xtuc/long": "4.2.2" + "color-name": "~1.1.4" } }, - "@webassemblyjs/utf8": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", - "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "@webassemblyjs/wasm-edit": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", - "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/helper-wasm-section": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-opt": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", - "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", - "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "pretty-format": { + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.3.0.tgz", + "integrity": "sha512-wToHwF8bkQknIcFkBqNfKu4+UZqnrLn/Vr+wwKQwwvPzkBfDDKp/qIabFqdgtoi5PEnM8LFByVsOrHoa3SpTVA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0" + "@jest/types": "^25.3.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" } }, - "@webassemblyjs/wasm-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", - "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + } + } + }, + "@types/tinycolor2": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.2.tgz", + "integrity": "sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw==", + "dev": true + }, + "@types/uglify-js": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.9.2.tgz", + "integrity": "sha512-d6dIfpPbF+8B7WiCi2ELY7m0w1joD8cRW4ms88Emdb2w062NeEpbNCeWwVCgzLRpVG+5e74VFSg4rgJ2xXjEiQ==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@types/unist": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", + "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==", + "dev": true + }, + "@types/uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==", + "dev": true + }, + "@types/vfile": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/vfile/-/vfile-3.0.2.tgz", + "integrity": "sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/unist": "*", + "@types/vfile-message": "*" + } + }, + "@types/vfile-message": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/vfile-message/-/vfile-message-1.0.1.tgz", + "integrity": "sha512-mlGER3Aqmq7bqR1tTTIVHq8KSAFFRyGbrxuM8C/H82g6k7r2fS+IMEkIu3D7JHzG10NvPdR8DNx0jr0pwpp4dA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/unist": "*" + } + }, + "@types/webpack": { + "version": "4.41.16", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.16.tgz", + "integrity": "sha512-w80nXwCcXwwgv7rkTXb8lET6nWPNNUJxa36lrA2DEkD5TcPpHrlGAPrjdpZnkFX/FXSSuN5IIxCYowAB1Vobtw==", + "dev": true, + "requires": { + "@types/anymatch": "*", + "@types/node": "*", + "@types/tapable": "*", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@types/webpack-env": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.15.2.tgz", + "integrity": "sha512-67ZgZpAlhIICIdfQrB5fnDvaKFcDxpKibxznfYRVAT4mQE41Dido/3Ty+E3xGBmTogc5+0Qb8tWhna+5B8z1iQ==", + "dev": true + }, + "@types/webpack-sources": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.7.tgz", + "integrity": "sha512-XyaHrJILjK1VHVC4aVlKsdNN5KBTwufMb43cQs+flGxtPAf/1Qwl8+Q0tp5BwEGaI8D6XT1L+9bSWXckgkjTLw==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@types/yargs": { + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.4.tgz", + "integrity": "sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg==", + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-13.1.0.tgz", + "integrity": "sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg==" + }, + "@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.26.0.tgz", + "integrity": "sha512-RELVoH5EYd+JlGprEyojUv9HeKcZqF7nZUGSblyAw1FwOGNnmQIU8kxJ69fttQvEwCsX5D6ECJT8GTozxrDKVQ==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "2.26.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + }, + "dependencies": { + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" } - }, - "@webassemblyjs/wast-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", - "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + } + } + }, + "@typescript-eslint/typescript-estree": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.26.0.tgz", + "integrity": "sha512-3x4SyZCLB4zsKsjuhxDLeVJN6W29VwBnYpCsZ7vIdPel9ZqLfIZJgJXO47MNUkurGpQuIBALdPQKtsSnWpE1Yg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "eslint-visitor-keys": "^1.1.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^6.3.0", + "tsutils": "^3.17.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/floating-point-hex-parser": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-code-frame": "1.9.0", - "@webassemblyjs/helper-fsm": "1.9.0", - "@xtuc/long": "4.2.2" + "ms": "^2.1.1" } }, - "@webassemblyjs/wast-printer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", - "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0", - "@xtuc/long": "4.2.2" - } + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true }, - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, - "optional": true, "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { - "object.assign": "^4.1.0" + "is-extglob": "^2.1.1" } }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "dev": true, - "optional": true - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "optional": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "dev": true, - "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - } - } - }, - "camelcase": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", - "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", - "dev": true - }, + } + } + }, + "@webassemblyjs/ast": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", + "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/wast-parser": "1.8.5" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz", + "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz", + "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz", + "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==", + "dev": true + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz", + "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.8.5" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz", + "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz", + "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "mamacro": "^0.0.3" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz", + "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", + "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz", + "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz", + "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz", + "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz", + "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/helper-wasm-section": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5", + "@webassemblyjs/wasm-opt": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5", + "@webassemblyjs/wast-printer": "1.8.5" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz", + "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/ieee754": "1.8.5", + "@webassemblyjs/leb128": "1.8.5", + "@webassemblyjs/utf8": "1.8.5" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz", + "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz", + "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-api-error": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/ieee754": "1.8.5", + "@webassemblyjs/leb128": "1.8.5", + "@webassemblyjs/utf8": "1.8.5" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz", + "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/floating-point-hex-parser": "1.8.5", + "@webassemblyjs/helper-api-error": "1.8.5", + "@webassemblyjs/helper-code-frame": "1.8.5", + "@webassemblyjs/helper-fsm": "1.8.5", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz", + "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/wast-parser": "1.8.5", + "@xtuc/long": "4.2.2" + } + }, + "@webpack-contrib/schema-utils": { + "version": "1.0.0-beta.0", + "resolved": "https://registry.npmjs.org/@webpack-contrib/schema-utils/-/schema-utils-1.0.0-beta.0.tgz", + "integrity": "sha512-LonryJP+FxQQHsjGBi6W786TQB1Oym+agTpY0c+Kj8alnIw+DLUJb6SI8Y1GHGhLCH1yPRrucjObUmxNICQ1pg==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "chalk": "^2.3.2", + "strip-ansi": "^4.0.0", + "text-table": "^0.2.0", + "webpack-log": "^1.1.2" + }, + "dependencies": { "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -12386,4413 +13069,90 @@ "supports-color": "^5.3.0" } }, - "chokidar": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", - "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", - "dev": true, - "optional": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.4.0" - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - } - }, - "create-react-context": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz", - "integrity": "sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==", - "dev": true, - "requires": { - "gud": "^1.0.0", - "warning": "^4.0.3" - } - }, - "css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "dev": true, - "requires": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "css-what": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.3.0.tgz", - "integrity": "sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg==", - "dev": true - }, - "csso": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.3.tgz", - "integrity": "sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==", - "dev": true, - "requires": { - "css-tree": "1.0.0-alpha.39" - }, - "dependencies": { - "css-tree": { - "version": "1.0.0-alpha.39", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz", - "integrity": "sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==", - "dev": true, - "requires": { - "mdn-data": "2.0.6", - "source-map": "^0.6.1" - } - }, - "mdn-data": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz", - "integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, - "enhanced-resolve": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", - "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "dependencies": { - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - } - } - }, - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - }, - "dependencies": { - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - } - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "dev": true }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "optional": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "webpack-log": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-1.2.0.tgz", + "integrity": "sha512-U9AnICnu50HXtiqiDxuli5gLB5PGBo7VvcHx36jRZHwK4vzOYLbImqT4lwWwoMHdQWwEKw736fCHEekokTEKHA==", "dev": true, - "optional": true, "requires": { - "is-glob": "^4.0.1" + "chalk": "^2.1.0", + "log-symbols": "^2.1.0", + "loglevelnext": "^1.0.1", + "uuid": "^3.1.0" } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "optional": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true - }, - "is-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", - "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "optional": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "optional": true - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "isobject": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", - "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", - "dev": true - }, - "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", - "dev": true - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "dev": true, - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "optional": true - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dev": true, - "requires": { - "boolbase": "~1.0.0" - } - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true - }, - "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } - } - }, - "polished": { - "version": "3.6.6", - "resolved": "https://registry.npmjs.org/polished/-/polished-3.6.6.tgz", - "integrity": "sha512-yiB2ims2DZPem0kCD6V0wnhcVGFEhNh0Iw0axNpKU+oSAgFt6yx6HxIT23Qg0WWvgS379cS35zT4AOyZZRzpQQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.9.2" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "qs": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", - "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", - "dev": true - }, - "readdirp": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", - "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", - "dev": true, - "optional": true, - "requires": { - "picomatch": "^2.2.1" - }, - "dependencies": { - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true, - "optional": true - } - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1" - } - }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - } - }, - "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true - }, - "telejson": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/telejson/-/telejson-5.0.2.tgz", - "integrity": "sha512-XCrDHGbinczsscs8LXFr9jDhvy37yBk9piB7FJrCfxE8oP66WDkolNMpaBkWYgQqB9dQGBGtTDzGQPedc9KJmw==", - "dev": true, - "requires": { - "@types/is-function": "^1.0.0", - "global": "^4.4.0", - "is-function": "^1.0.2", - "is-regex": "^1.1.1", - "is-symbol": "^1.0.3", - "isobject": "^4.0.0", - "lodash": "^4.17.19", - "memoizerific": "^1.11.3" - } - }, - "terser-webpack-plugin": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", - "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", - "dev": true, - "requires": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "optional": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "ts-dedent": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-1.1.1.tgz", - "integrity": "sha512-UGTRZu1evMw4uTPyYF66/KFd22XiU+jMaIuHrkIHQ2GivAXVlLV0v/vHrpOuTRf9BmpNHi/SO7Vd0rLu0y57jg==", - "dev": true - }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "requires": { - "unique-slug": "^2.0.0" - } - }, - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "watchpack": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz", - "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==", - "dev": true, - "requires": { - "chokidar": "^3.4.1", - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0", - "watchpack-chokidar2": "^2.0.0" - } - }, - "webpack": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.1.tgz", - "integrity": "sha512-4UOGAohv/VGUNQJstzEywwNxqX417FnjZgZJpJQegddzPmTvph37eBIRbRTfdySXzVtJXLJfbMN3mMYhM6GdmQ==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/wasm-edit": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^6.4.1", - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.3.0", - "eslint-scope": "^4.0.3", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.4.0", - "loader-utils": "^1.2.3", - "memory-fs": "^0.4.1", - "micromatch": "^3.1.10", - "mkdirp": "^0.5.3", - "neo-async": "^2.6.1", - "node-libs-browser": "^2.2.1", - "schema-utils": "^1.0.0", - "tapable": "^1.1.3", - "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.7.4", - "webpack-sources": "^1.4.1" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, - "@storybook/router": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-5.3.2.tgz", - "integrity": "sha512-EeM27i+89WS2mdT4j7RyVMSk7e7QiDb8bmyJAX3qyxg9ZOI4Wrvawgj6OAGuetItC1nayCPXFlXtIfHsP1h3lg==", - "dev": true, - "requires": { - "@reach/router": "^1.2.1", - "@storybook/csf": "0.0.1", - "@types/reach__router": "^1.2.3", - "core-js": "^3.0.1", - "global": "^4.3.2", - "lodash": "^4.17.15", - "memoizerific": "^1.11.3", - "qs": "^6.6.0", - "util-deprecate": "^1.0.2" - }, - "dependencies": { - "qs": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.1.tgz", - "integrity": "sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==", - "dev": true - } - } - }, - "@storybook/source-loader": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-5.3.2.tgz", - "integrity": "sha512-bHEWLXTSn89jip/fy/rjf2Psrmu4ZdquOD/SmB+WVuKRYE8JxcN1ybNUOhM7+XqekzIV/iw9rivhjGO1nZ7HAQ==", - "dev": true, - "requires": { - "@storybook/addons": "5.3.2", - "@storybook/client-logger": "5.3.2", - "@storybook/csf": "0.0.1", - "core-js": "^3.0.1", - "estraverse": "^4.2.0", - "global": "^4.3.2", - "loader-utils": "^1.2.3", - "prettier": "^1.16.4", - "prop-types": "^15.7.2", - "regenerator-runtime": "^0.13.3" - }, - "dependencies": { - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^2.0.0", - "json5": "^1.0.1" - } - }, - "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", - "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", - "dev": true - } - } - }, - "@storybook/theming": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-5.3.2.tgz", - "integrity": "sha512-WzFVgE2v/0mlK/5CqM9kSDdCMag7uqFsjI5oe3HCWEpwrUH70ndhuVZTVpyx0fscdL74vT5XZieoy2WwpVBl5Q==", - "dev": true, - "requires": { - "@emotion/core": "^10.0.20", - "@emotion/styled": "^10.0.17", - "@storybook/client-logger": "5.3.2", - "core-js": "^3.0.1", - "deep-object-diff": "^1.1.0", - "emotion-theming": "^10.0.19", - "global": "^4.3.2", - "memoizerific": "^1.11.3", - "polished": "^3.3.1", - "prop-types": "^15.7.2", - "resolve-from": "^5.0.0", - "ts-dedent": "^1.1.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "@storybook/ui": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/ui/-/ui-6.0.21.tgz", - "integrity": "sha512-50QYF8tHUgpVq7B7PWp7kmyf79NySWJO0piQFjHv027vV8GfbXMWVswAXwo3IfCihPlnLKe01WbsigM/9T1HCQ==", - "dev": true, - "requires": { - "@emotion/core": "^10.0.20", - "@storybook/addons": "6.0.21", - "@storybook/api": "6.0.21", - "@storybook/channels": "6.0.21", - "@storybook/client-logger": "6.0.21", - "@storybook/components": "6.0.21", - "@storybook/core-events": "6.0.21", - "@storybook/router": "6.0.21", - "@storybook/semver": "^7.3.2", - "@storybook/theming": "6.0.21", - "@types/markdown-to-jsx": "^6.11.0", - "copy-to-clipboard": "^3.0.8", - "core-js": "^3.0.1", - "core-js-pure": "^3.0.1", - "emotion-theming": "^10.0.19", - "fuse.js": "^3.6.1", - "global": "^4.3.2", - "lodash": "^4.17.15", - "markdown-to-jsx": "^6.11.4", - "memoizerific": "^1.11.3", - "polished": "^3.4.4", - "qs": "^6.6.0", - "react": "^16.8.3", - "react-dom": "^16.8.3", - "react-draggable": "^4.0.3", - "react-helmet-async": "^1.0.2", - "react-hotkeys": "2.0.0", - "react-sizeme": "^2.6.7", - "regenerator-runtime": "^0.13.3", - "resolve-from": "^5.0.0", - "store2": "^2.7.1" - }, - "dependencies": { - "@emotion/is-prop-valid": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", - "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", - "dev": true, - "requires": { - "@emotion/memoize": "0.7.4" - } - }, - "@emotion/memoize": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", - "dev": true - }, - "@reach/router": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@reach/router/-/router-1.3.4.tgz", - "integrity": "sha512-+mtn9wjlB9NN2CNnnC/BRYtwdKBfSyyasPYraNAyvaV1occr/5NnB4CVzjEZipNHwYebQwcndGUmpFzxAUoqSA==", - "dev": true, - "requires": { - "create-react-context": "0.3.0", - "invariant": "^2.2.3", - "prop-types": "^15.6.1", - "react-lifecycles-compat": "^3.0.4" - } - }, - "@storybook/addons": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.0.21.tgz", - "integrity": "sha512-yDttNLc3vXqBxwK795ykgzTC6MpvuXDQuF4LHSlHZQe6wsMu1m3fljnbYdafJWdx6cNZwUblU3KYcR11PqhkPg==", - "dev": true, - "requires": { - "@storybook/api": "6.0.21", - "@storybook/channels": "6.0.21", - "@storybook/client-logger": "6.0.21", - "@storybook/core-events": "6.0.21", - "@storybook/router": "6.0.21", - "@storybook/theming": "6.0.21", - "core-js": "^3.0.1", - "global": "^4.3.2", - "regenerator-runtime": "^0.13.3" - } - }, - "@storybook/api": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/api/-/api-6.0.21.tgz", - "integrity": "sha512-cRRGf/KGFwYiDouTouEcDdp45N1AbYnAfvLqYZ3KuUTGZ+CiU/PN/vavkp07DQeM4FIQO8TLhzHdsLFpLT7Lkw==", - "dev": true, - "requires": { - "@reach/router": "^1.3.3", - "@storybook/channels": "6.0.21", - "@storybook/client-logger": "6.0.21", - "@storybook/core-events": "6.0.21", - "@storybook/csf": "0.0.1", - "@storybook/router": "6.0.21", - "@storybook/semver": "^7.3.2", - "@storybook/theming": "6.0.21", - "@types/reach__router": "^1.3.5", - "core-js": "^3.0.1", - "fast-deep-equal": "^3.1.1", - "global": "^4.3.2", - "lodash": "^4.17.15", - "memoizerific": "^1.11.3", - "react": "^16.8.3", - "regenerator-runtime": "^0.13.3", - "store2": "^2.7.1", - "telejson": "^5.0.2", - "ts-dedent": "^1.1.1", - "util-deprecate": "^1.0.2" - } - }, - "@storybook/channels": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.0.21.tgz", - "integrity": "sha512-G6gjcEotSwDmOlxSmOMgsO3VhQ42RLJK7kFp6D5eg0Q6S8vsypltdT8orxdu+6+AbcBrL+5Sla8lThzaCvXsVQ==", - "dev": true, - "requires": { - "core-js": "^3.0.1", - "ts-dedent": "^1.1.1", - "util-deprecate": "^1.0.2" - } - }, - "@storybook/client-logger": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.0.21.tgz", - "integrity": "sha512-8aUEbhjXV+UMYQWukVYnp+kZafF+LD4Dm7eMo37IUZvt3VIjV1VvhxIDVJtqjk2vv0KZTepESFBkZQLmBzI9Zg==", - "dev": true, - "requires": { - "core-js": "^3.0.1", - "global": "^4.3.2" - } - }, - "@storybook/components": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-6.0.21.tgz", - "integrity": "sha512-r6btqFW/rcXIU5v231EifZfdh9O0fy7bJDXwwDf8zVUgLx8JRc0VnSs3nvK3Is9HF1wZ9vjx/7Lh4rTIDZAjgg==", - "dev": true, - "requires": { - "@storybook/client-logger": "6.0.21", - "@storybook/csf": "0.0.1", - "@storybook/theming": "6.0.21", - "@types/overlayscrollbars": "^1.9.0", - "@types/react-color": "^3.0.1", - "@types/react-syntax-highlighter": "11.0.4", - "core-js": "^3.0.1", - "fast-deep-equal": "^3.1.1", - "global": "^4.3.2", - "lodash": "^4.17.15", - "markdown-to-jsx": "^6.11.4", - "memoizerific": "^1.11.3", - "overlayscrollbars": "^1.10.2", - "polished": "^3.4.4", - "popper.js": "^1.14.7", - "react": "^16.8.3", - "react-color": "^2.17.0", - "react-dom": "^16.8.3", - "react-popper-tooltip": "^2.11.0", - "react-syntax-highlighter": "^12.2.1", - "react-textarea-autosize": "^8.1.1", - "ts-dedent": "^1.1.1" - } - }, - "@storybook/core-events": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.0.21.tgz", - "integrity": "sha512-p84fbPcsAhnqDhp+HJ4P8+vI2BqJus4IRoVAemLAwuPjyPElrV9UvOa/RHy1BN8Z6jXwFA+FFzfGl2kPJ3WYcA==", - "dev": true, - "requires": { - "core-js": "^3.0.1" - } - }, - "@storybook/router": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.0.21.tgz", - "integrity": "sha512-46SsKJfcd12lRrISnfrWhicJx8EylkgGDGohfH0n5p7inkkGOkKV8QFZoYPRKZueMXmUKpzJ0Z3HmVsLTCrCDw==", - "dev": true, - "requires": { - "@reach/router": "^1.3.3", - "@types/reach__router": "^1.3.5", - "core-js": "^3.0.1", - "global": "^4.3.2", - "memoizerific": "^1.11.3", - "qs": "^6.6.0" - } - }, - "@storybook/semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", - "dev": true, - "requires": { - "core-js": "^3.6.5", - "find-up": "^4.1.0" - }, - "dependencies": { - "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", - "dev": true - } - } - }, - "@storybook/theming": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.0.21.tgz", - "integrity": "sha512-n97DfB9kG6WrV1xBGDyeQibTrh8pBBCp3dSL3UTGH+KX3C2+4sm6QHlTgyekbi5FrbFEbnuZOKAS3YbLVONsRQ==", - "dev": true, - "requires": { - "@emotion/core": "^10.0.20", - "@emotion/is-prop-valid": "^0.8.6", - "@emotion/styled": "^10.0.17", - "@storybook/client-logger": "6.0.21", - "core-js": "^3.0.1", - "deep-object-diff": "^1.1.0", - "emotion-theming": "^10.0.19", - "global": "^4.3.2", - "memoizerific": "^1.11.3", - "polished": "^3.4.4", - "resolve-from": "^5.0.0", - "ts-dedent": "^1.1.1" - } - }, - "@types/reach__router": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/reach__router/-/reach__router-1.3.5.tgz", - "integrity": "sha512-h0NbqXN/tJuBY/xggZSej1SKQEstbHO7J/omt1tYoFGmj3YXOodZKbbqD4mNDh7zvEGYd7YFrac1LTtAr3xsYQ==", - "dev": true, - "requires": { - "@types/history": "*", - "@types/react": "*" - } - }, - "@types/react-syntax-highlighter": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.4.tgz", - "integrity": "sha512-9GfTo3a0PHwQeTVoqs0g5bS28KkSY48pp5659wA+Dp4MqceDEa8EHBqrllJvvtyusszyJhViUEap0FDvlk/9Zg==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "create-react-context": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz", - "integrity": "sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==", - "dev": true, - "requires": { - "gud": "^1.0.0", - "warning": "^4.0.3" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "highlight.js": { - "version": "9.15.10", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.10.tgz", - "integrity": "sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw==", - "dev": true - }, - "is-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", - "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", - "dev": true - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "isobject": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", - "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lowlight": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.12.1.tgz", - "integrity": "sha512-OqaVxMGIESnawn+TU/QMV5BJLbUghUfjDWPAtFqDYDmDtr4FnB+op8xM+pR7nKlauHNUHXGt0VgWatFB8voS5w==", - "dev": true, - "requires": { - "fault": "^1.0.2", - "highlight.js": "~9.15.0" - } - }, - "markdown-to-jsx": { - "version": "6.11.4", - "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-6.11.4.tgz", - "integrity": "sha512-3lRCD5Sh+tfA52iGgfs/XZiw33f7fFX9Bn55aNnVNUd2GzLDkOWyKYYD8Yju2B1Vn+feiEdgJs8T6Tg0xNokPw==", - "dev": true, - "requires": { - "prop-types": "^15.6.2", - "unquote": "^1.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "polished": { - "version": "3.6.6", - "resolved": "https://registry.npmjs.org/polished/-/polished-3.6.6.tgz", - "integrity": "sha512-yiB2ims2DZPem0kCD6V0wnhcVGFEhNh0Iw0axNpKU+oSAgFt6yx6HxIT23Qg0WWvgS379cS35zT4AOyZZRzpQQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.9.2" - } - }, - "qs": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", - "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", - "dev": true - }, - "react-popper-tooltip": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/react-popper-tooltip/-/react-popper-tooltip-2.11.1.tgz", - "integrity": "sha512-04A2f24GhyyMicKvg/koIOQ5BzlrRbKiAgP6L+Pdj1MVX3yJ1NeZ8+EidndQsbejFT55oW1b++wg2Z8KlAyhfQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.9.2", - "react-popper": "^1.3.7" - } - }, - "react-syntax-highlighter": { - "version": "12.2.1", - "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz", - "integrity": "sha512-CTsp0ZWijwKRYFg9xhkWD4DSpQqE4vb2NKVMdPAkomnILSmsNBHE0n5GuI5zB+PU3ySVvXvdt9jo+ViD9XibCA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "highlight.js": "~9.15.1", - "lowlight": "1.12.1", - "prismjs": "^1.8.4", - "refractor": "^2.4.1" - } - }, - "react-textarea-autosize": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.2.0.tgz", - "integrity": "sha512-grajUlVbkx6VdtSxCgzloUIphIZF5bKr21OYMceWPKkniy7H0mRAT/AXPrRtObAe+zUePnNlBwUc4ivVjUGIjw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.10.2", - "use-composed-ref": "^1.0.0", - "use-latest": "^1.0.0" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - } - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "telejson": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/telejson/-/telejson-5.0.2.tgz", - "integrity": "sha512-XCrDHGbinczsscs8LXFr9jDhvy37yBk9piB7FJrCfxE8oP66WDkolNMpaBkWYgQqB9dQGBGtTDzGQPedc9KJmw==", - "dev": true, - "requires": { - "@types/is-function": "^1.0.0", - "global": "^4.4.0", - "is-function": "^1.0.2", - "is-regex": "^1.1.1", - "is-symbol": "^1.0.3", - "isobject": "^4.0.0", - "lodash": "^4.17.19", - "memoizerific": "^1.11.3" - } - }, - "ts-dedent": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-1.1.1.tgz", - "integrity": "sha512-UGTRZu1evMw4uTPyYF66/KFd22XiU+jMaIuHrkIHQ2GivAXVlLV0v/vHrpOuTRf9BmpNHi/SO7Vd0rLu0y57jg==", - "dev": true - }, - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - } - } - }, - "@stylelint/postcss-css-in-js": { - "version": "0.37.1", - "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.1.tgz", - "integrity": "sha512-UMf2Rni3JGKi3ZwYRGMYJ5ipOA5ENJSKMtYA/pE1ZLURwdh7B5+z2r73RmWvub+N0UuH1Lo+TGfCgYwPvqpXNw==", - "dev": true, - "requires": { - "@babel/core": ">=7.9.0" - } - }, - "@stylelint/postcss-markdown": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/@stylelint/postcss-markdown/-/postcss-markdown-0.36.1.tgz", - "integrity": "sha512-iDxMBWk9nB2BPi1VFQ+Dc5+XpvODBHw2n3tYpaBZuEAFQlbtF9If0Qh5LTTwSi/XwdbJ2jt+0dis3i8omyggpw==", - "dev": true, - "requires": { - "remark": "^12.0.0", - "unist-util-find-all-after": "^3.0.1" - }, - "dependencies": { - "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", - "dev": true - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - }, - "markdown-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", - "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", - "dev": true, - "requires": { - "repeat-string": "^1.0.0" - } - }, - "mdast-util-compact": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz", - "integrity": "sha512-7GlnT24gEwDrdAwEHrU4Vv5lLWrEer4KOkAiKT9nYstsTad7Oc1TwqT2zIMKRdZF7cTuaf+GA1E4Kv7jJh8mPA==", - "dev": true, - "requires": { - "unist-util-visit": "^2.0.0" - } - }, - "parse-entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", - "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", - "dev": true, - "requires": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - } - }, - "remark": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/remark/-/remark-12.0.0.tgz", - "integrity": "sha512-oX4lMIS0csgk8AEbzY0h2jdR0ngiCHOpwwpxjmRa5TqAkeknY+tkhjRJGZqnCmvyuWh55/0SW5WY3R3nn3PH9A==", - "dev": true, - "requires": { - "remark-parse": "^8.0.0", - "remark-stringify": "^8.0.0", - "unified": "^9.0.0" - } - }, - "remark-parse": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.2.tgz", - "integrity": "sha512-eMI6kMRjsAGpMXXBAywJwiwAse+KNpmt+BK55Oofy4KvBZEqUDj6mWbGLJZrujoPIPPxDXzn3T9baRlpsm2jnQ==", - "dev": true, - "requires": { - "ccount": "^1.0.0", - "collapse-white-space": "^1.0.2", - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-whitespace-character": "^1.0.0", - "is-word-character": "^1.0.0", - "markdown-escapes": "^1.0.0", - "parse-entities": "^2.0.0", - "repeat-string": "^1.5.4", - "state-toggle": "^1.0.0", - "trim": "0.0.1", - "trim-trailing-lines": "^1.0.0", - "unherit": "^1.0.4", - "unist-util-remove-position": "^2.0.0", - "vfile-location": "^3.0.0", - "xtend": "^4.0.1" - } - }, - "remark-stringify": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-8.1.0.tgz", - "integrity": "sha512-FSPZv1ds76oAZjurhhuV5qXSUSoz6QRPuwYK38S41sLHwg4oB7ejnmZshj7qwjgYLf93kdz6BOX9j5aidNE7rA==", - "dev": true, - "requires": { - "ccount": "^1.0.0", - "is-alphanumeric": "^1.0.0", - "is-decimal": "^1.0.0", - "is-whitespace-character": "^1.0.0", - "longest-streak": "^2.0.1", - "markdown-escapes": "^1.0.0", - "markdown-table": "^2.0.0", - "mdast-util-compact": "^2.0.0", - "parse-entities": "^2.0.0", - "repeat-string": "^1.5.4", - "state-toggle": "^1.0.0", - "stringify-entities": "^3.0.0", - "unherit": "^1.0.4", - "xtend": "^4.0.1" - } - }, - "stringify-entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.0.1.tgz", - "integrity": "sha512-Lsk3ISA2++eJYqBMPKcr/8eby1I6L0gP0NlxF8Zja6c05yr/yCYyb2c9PwXjd08Ib3If1vn1rbs1H5ZtVuOfvQ==", - "dev": true, - "requires": { - "character-entities-html4": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.2", - "is-hexadecimal": "^1.0.0" - } - }, - "unified": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-9.0.0.tgz", - "integrity": "sha512-ssFo33gljU3PdlWLjNp15Inqb77d6JnJSfyplGJPT/a+fNRNyCBeveBAYJdO5khKdF6WVHa/yYCC7Xl6BDwZUQ==", - "dev": true, - "requires": { - "bail": "^1.0.0", - "extend": "^3.0.0", - "is-buffer": "^2.0.0", - "is-plain-obj": "^2.0.0", - "trough": "^1.0.0", - "vfile": "^4.0.0" - } - }, - "unist-util-is": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", - "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==", - "dev": true - }, - "unist-util-remove-position": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz", - "integrity": "sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==", - "dev": true, - "requires": { - "unist-util-visit": "^2.0.0" - } - }, - "unist-util-stringify-position": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", - "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", - "dev": true, - "requires": { - "@types/unist": "^2.0.2" - } - }, - "unist-util-visit": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.2.tgz", - "integrity": "sha512-HoHNhGnKj6y+Sq+7ASo2zpVdfdRifhTgX2KTU3B/sO/TTlZchp7E3S4vjRzDJ7L60KmrCPsQkVK3lEF3cz36XQ==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" - } - }, - "unist-util-visit-parents": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.0.2.tgz", - "integrity": "sha512-yJEfuZtzFpQmg1OSCyS9M5NJRrln/9FbYosH3iW0MG402QbdbaB8ZESwUv9RO6nRfLAKvWcMxCwdLWOov36x/g==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0" - } - }, - "vfile": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.1.1.tgz", - "integrity": "sha512-lRjkpyDGjVlBA7cDQhQ+gNcvB1BGaTHYuSOcY3S7OhDmBtnzX95FhtZZDecSTDm6aajFymyve6S5DN4ZHGezdQ==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "is-buffer": "^2.0.0", - "replace-ext": "1.0.0", - "unist-util-stringify-position": "^2.0.0", - "vfile-message": "^2.0.0" - } - }, - "vfile-location": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.0.1.tgz", - "integrity": "sha512-yYBO06eeN/Ki6Kh1QAkgzYpWT1d3Qln+ZCtSbJqFExPl1S3y2qqotJQXoh6qEvl/jDlgpUJolBn3PItVnnZRqQ==", - "dev": true - }, - "vfile-message": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", - "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" - } - } - } - }, - "@svgr/babel-plugin-add-jsx-attribute": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.0.1.tgz", - "integrity": "sha512-av76JbSudaN2CUOGuKQp5BVqLFidtojg4ApRTg1PBOVsskXK2ORwKnBYhIu0JLA6ynmuNDprlHNCD6IwLiYidw==", - "dev": true - }, - "@svgr/babel-plugin-remove-jsx-attribute": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.0.1.tgz", - "integrity": "sha512-oXyByaDQEK4Ut/eC75698MDKnaadbWmp/LS2w22cZAaoObCkkiwYYgZTZ+bvb3moo//AxvKkBtNrlz6+xBb9ZQ==", - "dev": true - }, - "@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", - "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==", - "dev": true - }, - "@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", - "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==", - "dev": true - }, - "@svgr/babel-plugin-svg-dynamic-title": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.0.1.tgz", - "integrity": "sha512-IbFiDBvq5WpANPndjEom1Y9k1pHCNfJs87jCN1Lt8NEA7yrNVPSoAjBVmmfi0aVBERfp8IT/lgjn2a/S85lXGg==", - "dev": true - }, - "@svgr/babel-plugin-svg-em-dimensions": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.0.1.tgz", - "integrity": "sha512-7kL9LtqCm1ca+zAbBsrD4ME3EQeVcRxkdrf2GsbKPgkzWJ+399vS4VqCP462+WvFRbG13jSwpNCrvhekdyvXsA==", - "dev": true - }, - "@svgr/babel-plugin-transform-react-native-svg": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.0.1.tgz", - "integrity": "sha512-ITG1jJ0zHQ4yft6ISQqlMW4fHIzsrSB/FmrMxAcJtkTjh9M2/9M8wfKxQya9NnTfZ5WMSlQjXMQNZmGQsuxRrw==", - "dev": true - }, - "@svgr/babel-plugin-transform-svg-component": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.2.0.tgz", - "integrity": "sha512-t4dq0cNr7c8cuUu1Cwehai/0iXO3dV5876r2QRaLdgQF3C6XOK2vdTvNOwcJ3uRa92revSC3kGL8v8WgJrecRg==", - "dev": true - }, - "@svgr/babel-preset": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.2.0.tgz", - "integrity": "sha512-sr7486h+SddMU1VgFrajXx/Ws0a1QPzX4wUBM1LgG2PHeZpnm+fQs2MXQNdnfoXRwo7C5mH2I4QDiRVR/49BEg==", - "dev": true, - "requires": { - "@svgr/babel-plugin-add-jsx-attribute": "^5.0.1", - "@svgr/babel-plugin-remove-jsx-attribute": "^5.0.1", - "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", - "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", - "@svgr/babel-plugin-svg-dynamic-title": "^5.0.1", - "@svgr/babel-plugin-svg-em-dimensions": "^5.0.1", - "@svgr/babel-plugin-transform-react-native-svg": "^5.0.1", - "@svgr/babel-plugin-transform-svg-component": "^5.2.0" - } - }, - "@svgr/core": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.2.0.tgz", - "integrity": "sha512-vuODnJ0owj/oFi2bzskuSEk6TGuYoMV9hmvBhGuE1QktzMAAjOr0LnvUN5u2eGB6ilGdI7yqUKrZtQ0Tw44mrA==", - "dev": true, - "requires": { - "@svgr/plugin-jsx": "^5.2.0", - "camelcase": "^5.3.1", - "cosmiconfig": "^6.0.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - } - }, - "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", - "lines-and-columns": "^1.1.6" - } - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - } - } - }, - "@svgr/hast-util-to-babel-ast": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.0.1.tgz", - "integrity": "sha512-G7UHNPNhLyDK5p6RJvSh4TRpHszTxG8jPp5lAxC6Ez6O6rj1plEAjrCDdYj50mvilUuT9IKjqn87F8+agpKaSw==", - "dev": true, - "requires": { - "@babel/types": "^7.4.4" - } - }, - "@svgr/plugin-jsx": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.2.0.tgz", - "integrity": "sha512-z1HWitE5sCNgaXqBGrmCnnnvR/BRTq9B/lsgZ+T8OWABzZHhezqjjDUvkyyyBb3Y+0xExWg5aTh2jxqk7GR9tg==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@svgr/babel-preset": "^5.2.0", - "@svgr/hast-util-to-babel-ast": "^5.0.1", - "svg-parser": "^2.0.2" - } - }, - "@svgr/plugin-svgo": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.2.0.tgz", - "integrity": "sha512-cyqWx026uO3heGG/55j5zfJLtS5sl0dWYawN1JotOqpJDyyR7rraTsnydpwwsOoz0YpESjVjAkXOAfd41lBY9Q==", - "dev": true, - "requires": { - "cosmiconfig": "^6.0.0", - "merge-deep": "^3.0.2", - "svgo": "^1.2.2" - }, - "dependencies": { - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - } - }, - "css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "dev": true, - "requires": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - } - }, - "css-what": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz", - "integrity": "sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==", - "dev": true - }, - "csso": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.2.tgz", - "integrity": "sha512-kS7/oeNVXkHWxby5tHVxlhjizRCSv8QdU7hB2FpdAibDU8FjTAolhNjKNTiLzXtUrKT6HwClE81yXwEk1309wg==", - "dev": true, - "requires": { - "css-tree": "1.0.0-alpha.37" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "es-abstract": { - "version": "1.17.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", - "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - }, - "dependencies": { - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - } - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", - "dev": true - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", - "dev": true - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dev": true, - "requires": { - "boolbase": "~1.0.0" - } - }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true - }, - "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", - "lines-and-columns": "^1.1.6" - } - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "string.prototype.trimleft": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", - "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "string.prototype.trimright": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", - "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - } - } - }, - "@svgr/webpack": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.2.0.tgz", - "integrity": "sha512-y9tMjTtqrvC998aCOgsEP8pb+bdg2RR1v8i+sjT31m4Xb8HGd905K7/GdSwEMM2nlTFbxSUQPZRRvfaJwAvghA==", - "dev": true, - "requires": { - "@babel/core": "^7.4.5", - "@babel/plugin-transform-react-constant-elements": "^7.0.0", - "@babel/preset-env": "^7.4.5", - "@babel/preset-react": "^7.0.0", - "@svgr/core": "^5.2.0", - "@svgr/plugin-jsx": "^5.2.0", - "@svgr/plugin-svgo": "^5.2.0", - "loader-utils": "^1.2.3" - }, - "dependencies": { - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } - } - }, - "@szmarczak/http-timer": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", - "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", - "dev": true, - "requires": { - "defer-to-connect": "^2.0.0" - } - }, - "@tannin/compile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@tannin/compile/-/compile-1.1.0.tgz", - "integrity": "sha512-n8m9eNDfoNZoxdvWiTfW/hSPhehzLJ3zW7f8E7oT6mCROoMNWCB4TYtv041+2FMAxweiE0j7i1jubQU4MEC/Gg==", - "requires": { - "@tannin/evaluate": "^1.2.0", - "@tannin/postfix": "^1.1.0" - } - }, - "@tannin/evaluate": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@tannin/evaluate/-/evaluate-1.2.0.tgz", - "integrity": "sha512-3ioXvNowbO/wSrxsDG5DKIMxC81P0QrQTYai8zFNY+umuoHWRPbQ/TuuDEOju9E+jQDXmj6yI5GyejNuh8I+eg==" - }, - "@tannin/plural-forms": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@tannin/plural-forms/-/plural-forms-1.1.0.tgz", - "integrity": "sha512-xl9R2mDZO/qiHam1AgMnAES6IKIg7OBhcXqy6eDsRCdXuxAFPcjrej9HMjyCLE0DJ/8cHf0i5OQTstuBRhpbHw==", - "requires": { - "@tannin/compile": "^1.1.0" - } - }, - "@tannin/postfix": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@tannin/postfix/-/postfix-1.1.0.tgz", - "integrity": "sha512-oocsqY7g0cR+Gur5jRQLSrX2OtpMLMse1I10JQBm8CdGMrDkh1Mg2gjsiquMHRtBs4Qwu5wgEp5GgIYHk4SNPw==" - }, - "@testing-library/dom": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.2.1.tgz", - "integrity": "sha512-xIGoHlQ2ZiEL1dJIFKNmLDypzYF+4OJTTASRctl/aoIDaS5y/pRVHRigoqvPUV11mdJoR71IIgi/6UviMgyz4g==", - "dev": true, - "requires": { - "@babel/runtime": "^7.9.2", - "@types/testing-library__dom": "^7.0.0", - "aria-query": "^4.0.2", - "dom-accessibility-api": "^0.4.2", - "pretty-format": "^25.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "aria-query": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.0.2.tgz", - "integrity": "sha512-S1G1V790fTaigUSM/Gd0NngzEfiMy9uTUfMyHhKhVyy4cH5O/eTuR01ydhGL0z4Za1PXFTRGH3qL8VhUQuEO5w==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.4", - "@babel/runtime-corejs3": "^7.7.4" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "pretty-format": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.3.0.tgz", - "integrity": "sha512-wToHwF8bkQknIcFkBqNfKu4+UZqnrLn/Vr+wwKQwwvPzkBfDDKp/qIabFqdgtoi5PEnM8LFByVsOrHoa3SpTVA==", - "dev": true, - "requires": { - "@jest/types": "^25.3.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^16.12.0" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, - "regenerator-runtime": { - "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" - } - } - }, - "@testing-library/react": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-10.0.2.tgz", - "integrity": "sha512-YT6Mw0oJz7R6vlEkmo1FlUD+K15FeXApOB5Ffm9zooFVnrwkt00w18dUJFMOh1yRp9wTdVRonbor7o4PIpFCmA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.9.2", - "@testing-library/dom": "^7.1.0", - "@types/testing-library__react": "^10.0.0" - }, - "dependencies": { - "regenerator-runtime": { - "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" - } - } - }, - "@types/anymatch": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", - "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", - "dev": true - }, - "@types/archiver": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-3.1.1.tgz", - "integrity": "sha512-TzVZ9204sH1TuFylfr1cw/AA/3/VldAAXswEwKLXUOzA9mDg+m6gHF9EaqKNlozcjc6knX5m1KAqJzksPLSEfw==", - "dev": true, - "requires": { - "@types/glob": "*" - } - }, - "@types/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@types/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-8GAYQ1jDRUQkSpHzJUqXwAkYFOxuWAOGLhIR4aPd/Y/yL12Q/9m7LsKpHKlfKdNE/362Hc9wPI1Yh6opDfxVJg==", - "dev": true - }, - "@types/babel-types": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.7.tgz", - "integrity": "sha512-dBtBbrc+qTHy1WdfHYjBwRln4+LWqASWakLHsWHR2NWHIFkv4W3O070IGoGLEBrJBvct3r0L1BUPuvURi7kYUQ==", - "dev": true - }, - "@types/babel__core": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.7.tgz", - "integrity": "sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", - "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", - "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.11.tgz", - "integrity": "sha512-ddHK5icION5U6q11+tV2f9Mo6CZVuT8GJKld2q9LqHSZbvLbH34Kcu2yFGckZut453+eQU6btIA3RihmnRgI+Q==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/babylon": { - "version": "6.16.5", - "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.5.tgz", - "integrity": "sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==", - "dev": true, - "requires": { - "@types/babel-types": "*" - } - }, - "@types/braces": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.0.tgz", - "integrity": "sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==", - "dev": true - }, - "@types/cacheable-request": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", - "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", - "dev": true, - "requires": { - "@types/http-cache-semantics": "*", - "@types/keyv": "*", - "@types/node": "*", - "@types/responselike": "*" - } - }, - "@types/classnames": { - "version": "2.2.10", - "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.10.tgz", - "integrity": "sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ==", - "dev": true - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" - }, - "@types/eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-hqzmggoxkOubpgTdcOltkfc5N8IftRJqU70d1jbOISjjZVPvjcr+CLi2CI70hx1SUIRkLgpglTy9w28nGe2Hsw==", - "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/estree": { - "version": "0.0.44", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.44.tgz", - "integrity": "sha512-iaIVzr+w2ZJ5HkidlZ3EJM8VTZb2MJLCjw3V+505yVts0gRC4UMvjw0d1HPtGqI/HQC/KdsYtayfzl+AXY2R8g==", - "dev": true - }, - "@types/events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" - }, - "@types/fs-extra": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.4.tgz", - "integrity": "sha512-50GO5ez44lxK5MDH90DYHFFfqxH7+fTqEEnvguQRzJ/tY9qFrMSHLiYHite+F3SNmf7+LHC1eMXojuD+E3Qcyg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", - "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", - "dev": true, - "requires": { - "@types/events": "*", - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@types/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-pYHWiDR+EOUN18F9byiAoQNUMZ0=", - "dev": true - }, - "@types/graceful-fs": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz", - "integrity": "sha512-AiHRaEB50LQg0pZmm659vNBb9f4SJ0qrAnteuzhSeAUcJKxoYgEnprg/83kppCnc2zvtCKbdZry1a5pVY3lOTQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/hammerjs": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.36.tgz", - "integrity": "sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ==" - }, - "@types/history": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.4.tgz", - "integrity": "sha512-+o2igcuZA3xtOoFH56s+MCZVidwlJNcJID57DSCyawS2i910yG9vkwehCjJNZ6ImhCR5S9DbvIJKyYHcMyOfMw==", - "dev": true - }, - "@types/html-minifier-terser": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz", - "integrity": "sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA==", - "dev": true - }, - "@types/http-cache-semantics": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", - "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", - "dev": true - }, - "@types/is-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/is-function/-/is-function-1.0.0.tgz", - "integrity": "sha512-iTs9HReBu7evG77Q4EC8hZnqRt57irBDkK9nvmHroiOIVwYMQc4IvYvdRgwKfYepunIY7Oh/dBuuld+Gj9uo6w==", - "dev": true - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==" - }, - "@types/istanbul-lib-report": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz", - "integrity": "sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==", - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", - "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", - "requires": { - "@types/istanbul-lib-coverage": "*", - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "23.3.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", - "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", - "dev": true - }, - "@types/json-schema": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", - "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", - "dev": true - }, - "@types/keyv": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", - "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/lodash": { - "version": "4.14.149", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", - "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==", - "dev": true - }, - "@types/lodash.clonedeep": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.6.tgz", - "integrity": "sha512-cE1jYr2dEg1wBImvXlNtp0xDoS79rfEdGozQVgliDZj1uERH4k+rmEMTudP9b4VQ8O6nRb5gPqft0QzEQGMQgA==", - "dev": true, - "requires": { - "@types/lodash": "*" - } - }, - "@types/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@types/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-8G41YFhmOl8Ck6NrwLK5hhnbz6ADfuDJP+zusDnX3PoYhfC60+H/rQE6zmdO4yFzPCPJPY4oGZK2spbXm6gYEA==", - "dev": true, - "requires": { - "@types/lodash": "*" - } - }, - "@types/lodash.merge": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.6.tgz", - "integrity": "sha512-IB90krzMf7YpfgP3u/EvZEdXVvm4e3gJbUvh5ieuI+o+XqiNEt6fCzqNRaiLlPVScLI59RxIGZMQ3+Ko/DJ8vQ==", - "dev": true, - "requires": { - "@types/lodash": "*" - } - }, - "@types/markdown-to-jsx": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/@types/markdown-to-jsx/-/markdown-to-jsx-6.11.2.tgz", - "integrity": "sha512-ESuCu8Bk7jpTZ3YPdMW1+6wUj13F5N15vXfc7BuUAN0eCp0lrvVL9nzOTzoqvbRzXMciuqXr1KrHt3xQAhfwPA==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/micromatch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.1.tgz", - "integrity": "sha512-my6fLBvpY70KattTNzYOK6KU1oR1+UCz9ug/JbcF5UrEmeCt9P7DV2t7L8+t18mMPINqGQCE4O8PLOPbI84gxw==", - "dev": true, - "requires": { - "@types/braces": "*" - } - }, - "@types/mime-types": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.0.tgz", - "integrity": "sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM=", - "dev": true - }, - "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "dev": true - }, - "@types/minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", - "dev": true - }, - "@types/node": { - "version": "12.7.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.11.tgz", - "integrity": "sha512-Otxmr2rrZLKRYIybtdG/sgeO+tHY20GxeDjcGmUnmmlCWyEnv2a2x1ZXBo3BTec4OiTXMQCiazB8NMBf0iRlFw==", - "dev": true - }, - "@types/node-fetch": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz", - "integrity": "sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==", - "dev": true, - "requires": { - "@types/node": "*", - "form-data": "^3.0.0" - }, - "dependencies": { - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - } - } - }, - "@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", - "dev": true - }, - "@types/npm-package-arg": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/npm-package-arg/-/npm-package-arg-6.1.0.tgz", - "integrity": "sha512-vbt5fb0y1svMhu++1lwtKmZL76d0uPChFlw7kEzyUmTwfmpHRcFb8i0R8ElT69q/L+QLgK2hgECivIAvaEDwag==", - "dev": true - }, - "@types/npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@types/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-4QQmOF5KlwfxJ5IGXFIudkeLCdMABz03RcUXu+LCb24zmln8QW6aDjuGl4d4XPVLf2j+FnjelHTP7dvceAFbhA==", - "dev": true - }, - "@types/overlayscrollbars": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@types/overlayscrollbars/-/overlayscrollbars-1.12.0.tgz", - "integrity": "sha512-h/pScHNKi4mb+TrJGDon8Yb06ujFG0mSg12wIO0sWMUF3dQIe2ExRRdNRviaNt9IjxIiOfnRr7FsQAdHwK4sMg==", - "dev": true - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "@types/prettier": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.0.tgz", - "integrity": "sha512-gDE8JJEygpay7IjA/u3JiIURvwZW08f0cZSZLAzFoX/ZmeqvS0Sqv+97aKuHpNsalAMMhwPe+iAS6fQbfmbt7A==", - "dev": true - }, - "@types/prop-types": { - "version": "15.7.3", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", - "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" - }, - "@types/puppeteer": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.0.tgz", - "integrity": "sha512-zTYDLjnHjgzokrwKt7N0rgn7oZPYo1J0m8Ghu+gXqzLCEn8RWbELa2uprE2UFJ0jU/Sk0x9jXXdOH/5QQLFHhQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/puppeteer-core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/puppeteer-core/-/puppeteer-core-2.0.0.tgz", - "integrity": "sha512-JvoEb7KgEkUet009ZDrtpUER3hheXoHgQByuYpJZ5WWT7LWwMH+0NTqGQXGgoOKzs+G5NA1T4DZwXK79Bhnejw==", - "dev": true, - "requires": { - "@types/puppeteer": "*" - } - }, - "@types/q": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", - "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", - "dev": true - }, - "@types/qs": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.1.tgz", - "integrity": "sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw==", - "dev": true - }, - "@types/reach__router": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@types/reach__router/-/reach__router-1.2.6.tgz", - "integrity": "sha512-Oh5DAVr/L2svBvubw6QEFpXGu295Y406BPs4i9t1n2pp7M+q3pmCmhzb9oZV5wncR41KCD3NHl1Yhi7uKnTPsA==", - "dev": true, - "requires": { - "@types/history": "*", - "@types/react": "*" - } - }, - "@types/react": { - "version": "16.9.49", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.49.tgz", - "integrity": "sha512-DtLFjSj0OYAdVLBbyjhuV9CdGVHCkHn2R+xr3XkBvK2rS1Y1tkc14XSGjYgm5Fjjr90AxH9tiSzc1pCFMGO06g==", - "requires": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - }, - "dependencies": { - "csstype": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.3.tgz", - "integrity": "sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==" - } - } - }, - "@types/react-color": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.1.tgz", - "integrity": "sha512-J6mYm43Sid9y+OjZ7NDfJ2VVkeeuTPNVImNFITgQNXodHteKfl/t/5pAR5Z9buodZ2tCctsZjgiMlQOpfntakw==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/react-dom": { - "version": "16.9.8", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", - "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==", - "requires": { - "@types/react": "*" - } - }, - "@types/react-native": { - "version": "0.57.65", - "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.57.65.tgz", - "integrity": "sha512-7P5ulTb+/cnwbABWaAjzKmSYkRWeK7UCTfUwHhDpnwxdiL2X/KbdN1sPgo0B2E4zxfYE3MEoHv7FhB8Acfvf8A==", - "requires": { - "@types/prop-types": "*", - "@types/react": "*" - } - }, - "@types/react-syntax-highlighter": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.2.tgz", - "integrity": "sha512-iMNcixH8330f2dq0RY+VOXCP8JFehgmOhLOtnO85Ty+qu0fHXJNEqWx5VuFv8v0aEq0U/N9d/k1yvA+c6PEmPw==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/react-textarea-autosize": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@types/react-textarea-autosize/-/react-textarea-autosize-4.3.5.tgz", - "integrity": "sha512-PiDL83kPMTolyZAWW3lyzO6ktooTb9tFTntVy7CA83/qFLWKLJ5bLeRboy6J6j3b1e8h2Eec6gBTEOOJRjV14A==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/requestidlecallback": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@types/requestidlecallback/-/requestidlecallback-0.3.1.tgz", - "integrity": "sha512-BnnRkgWYijCIndUn+LgoqKHX/hNpJC5G03B9y7mZya/C2gUQTSn75fEj3ZP1/Rl2E6EYeXh2/7/8UNEZ4X7HuQ==", - "dev": true - }, - "@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/semver": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.2.0.tgz", - "integrity": "sha512-TbB0A8ACUWZt3Y6bQPstW9QNbhNeebdgLX4T/ZfkrswAfUzRiXrgd9seol+X379Wa589Pu4UEx9Uok0D4RjRCQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/source-list-map": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", - "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", - "dev": true - }, - "@types/sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@types/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-hkgzYF+qnIl8uTO8rmUSVSfQ8BIfMXC4yJAF4n8BE758YsKBZvFC4NumnAegj7KmylP0liEZNpb9RRGFMbFejA==", - "dev": true - }, - "@types/stack-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", - "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" - }, - "@types/tapable": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.5.tgz", - "integrity": "sha512-/gG2M/Imw7cQFp8PGvz/SwocNrmKFjFsm5Pb8HdbHkZ1K8pmuPzOX4VeVoiEecFCVf4CsN1r3/BRvx+6sNqwtQ==", - "dev": true - }, - "@types/testing-library__dom": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@types/testing-library__dom/-/testing-library__dom-7.0.1.tgz", - "integrity": "sha512-WokGRksRJb3Dla6h02/0/NNHTkjsj4S8aJZiwMj/5/UL8VZ1iCe3H8SHzfpmBeH8Vp4SPRT8iC2o9kYULFhDIw==", - "dev": true, - "requires": { - "pretty-format": "^25.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "pretty-format": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.3.0.tgz", - "integrity": "sha512-wToHwF8bkQknIcFkBqNfKu4+UZqnrLn/Vr+wwKQwwvPzkBfDDKp/qIabFqdgtoi5PEnM8LFByVsOrHoa3SpTVA==", - "dev": true, - "requires": { - "@jest/types": "^25.3.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^16.12.0" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - } - } - }, - "@types/testing-library__react": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@types/testing-library__react/-/testing-library__react-10.0.1.tgz", - "integrity": "sha512-RbDwmActAckbujLZeVO/daSfdL1pnjVqas25UueOkAY5r7vriavWf0Zqg7ghXMHa8ycD/kLkv8QOj31LmSYwww==", - "dev": true, - "requires": { - "@types/react-dom": "*", - "@types/testing-library__dom": "*", - "pretty-format": "^25.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "pretty-format": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.3.0.tgz", - "integrity": "sha512-wToHwF8bkQknIcFkBqNfKu4+UZqnrLn/Vr+wwKQwwvPzkBfDDKp/qIabFqdgtoi5PEnM8LFByVsOrHoa3SpTVA==", - "dev": true, - "requires": { - "@jest/types": "^25.3.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^16.12.0" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - } - } - }, - "@types/ua-parser-js": { - "version": "0.7.33", - "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.33.tgz", - "integrity": "sha512-ngUKcHnytUodUCL7C6EZ+lVXUjTMQb+9p/e1JjV5tN9TVzS98lHozWEFRPY1QcCdwFeMsmVWfZ3DPPT/udCyIw==", - "dev": true - }, - "@types/uglify-js": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.9.2.tgz", - "integrity": "sha512-d6dIfpPbF+8B7WiCi2ELY7m0w1joD8cRW4ms88Emdb2w062NeEpbNCeWwVCgzLRpVG+5e74VFSg4rgJ2xXjEiQ==", - "dev": true, - "requires": { - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "@types/unist": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", - "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==", - "dev": true - }, - "@types/uuid": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-7.0.2.tgz", - "integrity": "sha512-8Ly3zIPTnT0/8RCU6Kg/G3uTICf9sRwYOpUzSIM3503tLIKcnJPRuinHhXngJUy2MntrEf6dlpOHXJju90Qh5w==", - "dev": true - }, - "@types/vfile": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/vfile/-/vfile-3.0.2.tgz", - "integrity": "sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/unist": "*", - "@types/vfile-message": "*" - } - }, - "@types/vfile-message": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/vfile-message/-/vfile-message-1.0.1.tgz", - "integrity": "sha512-mlGER3Aqmq7bqR1tTTIVHq8KSAFFRyGbrxuM8C/H82g6k7r2fS+IMEkIu3D7JHzG10NvPdR8DNx0jr0pwpp4dA==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/unist": "*" - } - }, - "@types/webpack": { - "version": "4.41.16", - "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.16.tgz", - "integrity": "sha512-w80nXwCcXwwgv7rkTXb8lET6nWPNNUJxa36lrA2DEkD5TcPpHrlGAPrjdpZnkFX/FXSSuN5IIxCYowAB1Vobtw==", - "dev": true, - "requires": { - "@types/anymatch": "*", - "@types/node": "*", - "@types/tapable": "*", - "@types/uglify-js": "*", - "@types/webpack-sources": "*", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "@types/webpack-env": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.15.2.tgz", - "integrity": "sha512-67ZgZpAlhIICIdfQrB5fnDvaKFcDxpKibxznfYRVAT4mQE41Dido/3Ty+E3xGBmTogc5+0Qb8tWhna+5B8z1iQ==", - "dev": true - }, - "@types/webpack-sources": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.7.tgz", - "integrity": "sha512-XyaHrJILjK1VHVC4aVlKsdNN5KBTwufMb43cQs+flGxtPAf/1Qwl8+Q0tp5BwEGaI8D6XT1L+9bSWXckgkjTLw==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/source-list-map": "*", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "@types/yargs": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.4.tgz", - "integrity": "sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-13.1.0.tgz", - "integrity": "sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg==" - }, - "@types/yauzl": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", - "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", - "dev": true, - "optional": true, - "requires": { - "@types/node": "*" - } - }, - "@typescript-eslint/experimental-utils": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.26.0.tgz", - "integrity": "sha512-RELVoH5EYd+JlGprEyojUv9HeKcZqF7nZUGSblyAw1FwOGNnmQIU8kxJ69fttQvEwCsX5D6ECJT8GTozxrDKVQ==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "2.26.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" - }, - "dependencies": { - "eslint-scope": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", - "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - } - } - }, - "@typescript-eslint/typescript-estree": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.26.0.tgz", - "integrity": "sha512-3x4SyZCLB4zsKsjuhxDLeVJN6W29VwBnYpCsZ7vIdPel9ZqLfIZJgJXO47MNUkurGpQuIBALdPQKtsSnWpE1Yg==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "eslint-visitor-keys": "^1.1.0", - "glob": "^7.1.6", - "is-glob": "^4.0.1", - "lodash": "^4.17.15", - "semver": "^6.3.0", - "tsutils": "^3.17.1" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "eslint-visitor-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@webassemblyjs/ast": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", - "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", - "dev": true, - "requires": { - "@webassemblyjs/helper-module-context": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/wast-parser": "1.8.5" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz", - "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz", - "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz", - "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==", - "dev": true - }, - "@webassemblyjs/helper-code-frame": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz", - "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==", - "dev": true, - "requires": { - "@webassemblyjs/wast-printer": "1.8.5" - } - }, - "@webassemblyjs/helper-fsm": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz", - "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==", - "dev": true - }, - "@webassemblyjs/helper-module-context": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz", - "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "mamacro": "^0.0.3" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz", - "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", - "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz", - "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==", - "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz", - "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==", - "dev": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz", - "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==", - "dev": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz", - "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/helper-wasm-section": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5", - "@webassemblyjs/wasm-opt": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5", - "@webassemblyjs/wast-printer": "1.8.5" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz", - "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/ieee754": "1.8.5", - "@webassemblyjs/leb128": "1.8.5", - "@webassemblyjs/utf8": "1.8.5" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz", - "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz", - "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-api-error": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/ieee754": "1.8.5", - "@webassemblyjs/leb128": "1.8.5", - "@webassemblyjs/utf8": "1.8.5" - } - }, - "@webassemblyjs/wast-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz", - "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/floating-point-hex-parser": "1.8.5", - "@webassemblyjs/helper-api-error": "1.8.5", - "@webassemblyjs/helper-code-frame": "1.8.5", - "@webassemblyjs/helper-fsm": "1.8.5", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz", - "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/wast-parser": "1.8.5", - "@xtuc/long": "4.2.2" - } - }, - "@webpack-contrib/schema-utils": { - "version": "1.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@webpack-contrib/schema-utils/-/schema-utils-1.0.0-beta.0.tgz", - "integrity": "sha512-LonryJP+FxQQHsjGBi6W786TQB1Oym+agTpY0c+Kj8alnIw+DLUJb6SI8Y1GHGhLCH1yPRrucjObUmxNICQ1pg==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0", - "chalk": "^2.3.2", - "strip-ansi": "^4.0.0", - "text-table": "^0.2.0", - "webpack-log": "^1.1.2" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - }, - "webpack-log": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-1.2.0.tgz", - "integrity": "sha512-U9AnICnu50HXtiqiDxuli5gLB5PGBo7VvcHx36jRZHwK4vzOYLbImqT4lwWwoMHdQWwEKw736fCHEekokTEKHA==", - "dev": true, - "requires": { - "chalk": "^2.1.0", - "log-symbols": "^2.1.0", - "loglevelnext": "^1.0.1", - "uuid": "^3.1.0" - } - } - } - }, - "@wordpress/a11y": { - "version": "file:packages/a11y", - "requires": { - "@babel/runtime": "^7.11.2", - "@wordpress/dom-ready": "file:packages/dom-ready", - "@wordpress/i18n": "file:packages/i18n" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } - } - }, - "@wordpress/annotations": { - "version": "file:packages/annotations", - "requires": { - "@babel/runtime": "^7.11.2", - "@wordpress/data": "file:packages/data", - "@wordpress/hooks": "file:packages/hooks", - "@wordpress/i18n": "file:packages/i18n", - "@wordpress/rich-text": "file:packages/rich-text", - "lodash": "^4.17.19", - "rememo": "^3.0.0", - "uuid": "^7.0.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } - } - }, - "@wordpress/api-fetch": { - "version": "file:packages/api-fetch", - "requires": { - "@babel/runtime": "^7.11.2", - "@wordpress/i18n": "file:packages/i18n", - "@wordpress/url": "file:packages/url" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } - } - }, - "@wordpress/autop": { - "version": "file:packages/autop", - "requires": { - "@babel/runtime": "^7.11.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } - } - }, - "@wordpress/babel-plugin-import-jsx-pragma": { - "version": "file:packages/babel-plugin-import-jsx-pragma", - "dev": true - }, - "@wordpress/babel-plugin-makepot": { - "version": "file:packages/babel-plugin-makepot", - "dev": true, - "requires": { - "@babel/runtime": "^7.11.2", - "gettext-parser": "^1.3.1", - "lodash": "^4.17.19" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - } - } - }, - "@wordpress/babel-preset-default": { - "version": "file:packages/babel-preset-default", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/plugin-transform-react-jsx": "^7.10.4", - "@babel/plugin-transform-runtime": "^7.11.5", - "@babel/preset-env": "^7.11.5", - "@babel/runtime": "^7.11.2", - "@wordpress/babel-plugin-import-jsx-pragma": "file:packages/babel-plugin-import-jsx-pragma", - "@wordpress/browserslist-config": "file:packages/browserslist-config", - "@wordpress/element": "file:packages/element", - "@wordpress/warning": "file:packages/warning", - "core-js": "^3.6.4" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/compat-data": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz", - "integrity": "sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==", - "dev": true, - "requires": { - "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "semver": "^5.5.0" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", - "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", - "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", - "dev": true, - "requires": { - "@babel/helper-explode-assignable-expression": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-builder-react-jsx": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.4.tgz", - "integrity": "sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-builder-react-jsx-experimental": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.11.5.tgz", - "integrity": "sha512-Vc4aPJnRZKWfzeCBsqTBnzulVNjABVdahSPhtdMD3Vs80ykx4a87jTHtF/VR+alSrDmNvat7l13yrRHauGcHVw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-module-imports": "^7.10.4", - "@babel/types": "^7.11.5" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz", - "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.10.4", - "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "levenary": "^1.1.1", - "semver": "^5.5.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", - "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.10.5", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz", - "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-regex": "^7.10.4", - "regexpu-core": "^4.7.0" - } - }, - "@babel/helper-define-map": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", - "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/types": "^7.10.5", - "lodash": "^4.17.19" - } - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.11.4", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.11.4.tgz", - "integrity": "sha512-ux9hm3zR4WV1Y3xXxXkdG/0gxF9nvI0YVmKVhvK9AfMoaQkemL3sJpXw+Xbz65azo8qJiEz2XVDUpK3KYhH3ZQ==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", - "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", - "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", - "dev": true, - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", - "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-module-transforms": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", - "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-simple-access": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/template": "^7.10.4", - "@babel/types": "^7.11.0", - "lodash": "^4.17.19" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", - "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", - "dev": true - }, - "@babel/helper-regex": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", - "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", - "dev": true, - "requires": { - "lodash": "^4.17.19" - } - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.11.4", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz", - "integrity": "sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-wrap-function": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-replace-supers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", - "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-simple-access": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", - "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", - "dev": true, - "requires": { - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", - "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", - "dev": true, - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true - }, - "@babel/helper-wrap-function": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz", - "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", - "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", - "dev": true - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", - "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.10.4", - "@babel/plugin-syntax-async-generators": "^7.8.0" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", - "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz", - "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", - "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.0" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", - "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz", - "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz", - "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.10.4" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz", - "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz", - "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz", - "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", - "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", - "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz", - "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz", - "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.10.4" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz", - "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz", - "integrity": "sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz", - "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-define-map": "^7.10.4", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz", - "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz", - "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz", - "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz", - "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz", - "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==", - "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz", - "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz", - "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz", - "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz", - "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", - "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.10.5", - "@babel/helper-plugin-utils": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz", - "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-simple-access": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", - "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.10.4", - "@babel/helper-module-transforms": "^7.10.5", - "@babel/helper-plugin-utils": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", - "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", - "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", - "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", - "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", - "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", - "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.4.tgz", - "integrity": "sha512-L+MfRhWjX0eI7Js093MM6MacKU4M6dnCRa/QPDwYMxjljzSCzzlzKzj9Pk4P3OtrPcxr2N3znR419nr3Xw+65A==", - "dev": true, - "requires": { - "@babel/helper-builder-react-jsx": "^7.10.4", - "@babel/helper-builder-react-jsx-experimental": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-jsx": "^7.10.4" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", - "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", - "dev": true, - "requires": { - "regenerator-transform": "^0.14.2" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", - "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-runtime": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.11.5.tgz", - "integrity": "sha512-9aIoee+EhjySZ6vY5hnLjigHzunBlscx9ANKutkeWTJTx6m5Rbq6Ic01tLvO54lSusR+BxV7u4UDdCmXv5aagg==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "resolve": "^1.8.1", - "semver": "^5.5.1" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", - "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz", - "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", - "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-regex": "^7.10.4" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", - "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", - "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", - "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/preset-env": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz", - "integrity": "sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.11.0", - "@babel/helper-compilation-targets": "^7.10.4", - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-proposal-async-generator-functions": "^7.10.4", - "@babel/plugin-proposal-class-properties": "^7.10.4", - "@babel/plugin-proposal-dynamic-import": "^7.10.4", - "@babel/plugin-proposal-export-namespace-from": "^7.10.4", - "@babel/plugin-proposal-json-strings": "^7.10.4", - "@babel/plugin-proposal-logical-assignment-operators": "^7.11.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", - "@babel/plugin-proposal-numeric-separator": "^7.10.4", - "@babel/plugin-proposal-object-rest-spread": "^7.11.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", - "@babel/plugin-proposal-optional-chaining": "^7.11.0", - "@babel/plugin-proposal-private-methods": "^7.10.4", - "@babel/plugin-proposal-unicode-property-regex": "^7.10.4", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.10.4", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.10.4", - "@babel/plugin-transform-arrow-functions": "^7.10.4", - "@babel/plugin-transform-async-to-generator": "^7.10.4", - "@babel/plugin-transform-block-scoped-functions": "^7.10.4", - "@babel/plugin-transform-block-scoping": "^7.10.4", - "@babel/plugin-transform-classes": "^7.10.4", - "@babel/plugin-transform-computed-properties": "^7.10.4", - "@babel/plugin-transform-destructuring": "^7.10.4", - "@babel/plugin-transform-dotall-regex": "^7.10.4", - "@babel/plugin-transform-duplicate-keys": "^7.10.4", - "@babel/plugin-transform-exponentiation-operator": "^7.10.4", - "@babel/plugin-transform-for-of": "^7.10.4", - "@babel/plugin-transform-function-name": "^7.10.4", - "@babel/plugin-transform-literals": "^7.10.4", - "@babel/plugin-transform-member-expression-literals": "^7.10.4", - "@babel/plugin-transform-modules-amd": "^7.10.4", - "@babel/plugin-transform-modules-commonjs": "^7.10.4", - "@babel/plugin-transform-modules-systemjs": "^7.10.4", - "@babel/plugin-transform-modules-umd": "^7.10.4", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4", - "@babel/plugin-transform-new-target": "^7.10.4", - "@babel/plugin-transform-object-super": "^7.10.4", - "@babel/plugin-transform-parameters": "^7.10.4", - "@babel/plugin-transform-property-literals": "^7.10.4", - "@babel/plugin-transform-regenerator": "^7.10.4", - "@babel/plugin-transform-reserved-words": "^7.10.4", - "@babel/plugin-transform-shorthand-properties": "^7.10.4", - "@babel/plugin-transform-spread": "^7.11.0", - "@babel/plugin-transform-sticky-regex": "^7.10.4", - "@babel/plugin-transform-template-literals": "^7.10.4", - "@babel/plugin-transform-typeof-symbol": "^7.10.4", - "@babel/plugin-transform-unicode-escapes": "^7.10.4", - "@babel/plugin-transform-unicode-regex": "^7.10.4", - "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.11.5", - "browserslist": "^4.12.0", - "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", - "semver": "^5.5.0" - } - }, - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/types": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", - "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "requires": { - "object.assign": "^4.1.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true } } }, + "@wordpress/a11y": { + "version": "file:packages/a11y", + "requires": { + "@babel/runtime": "^7.12.5", + "@wordpress/dom-ready": "file:packages/dom-ready", + "@wordpress/i18n": "file:packages/i18n" + } + }, + "@wordpress/annotations": { + "version": "file:packages/annotations", + "requires": { + "@babel/runtime": "^7.12.5", + "@wordpress/data": "file:packages/data", + "@wordpress/hooks": "file:packages/hooks", + "@wordpress/i18n": "file:packages/i18n", + "@wordpress/rich-text": "file:packages/rich-text", + "lodash": "^4.17.19", + "rememo": "^3.0.0", + "uuid": "^8.3.0" + } + }, + "@wordpress/api-fetch": { + "version": "file:packages/api-fetch", + "requires": { + "@babel/runtime": "^7.12.5", + "@wordpress/i18n": "file:packages/i18n", + "@wordpress/url": "file:packages/url" + } + }, + "@wordpress/autop": { + "version": "file:packages/autop", + "requires": { + "@babel/runtime": "^7.12.5" + } + }, + "@wordpress/babel-plugin-import-jsx-pragma": { + "version": "file:packages/babel-plugin-import-jsx-pragma", + "dev": true + }, + "@wordpress/babel-plugin-makepot": { + "version": "file:packages/babel-plugin-makepot", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "gettext-parser": "^1.3.1", + "lodash": "^4.17.19" + } + }, + "@wordpress/babel-preset-default": { + "version": "file:packages/babel-preset-default", + "dev": true, + "requires": { + "@babel/core": "^7.12.9", + "@babel/plugin-transform-react-jsx": "^7.12.7", + "@babel/plugin-transform-runtime": "^7.12.1", + "@babel/preset-env": "^7.12.7", + "@babel/runtime": "^7.12.5", + "@wordpress/babel-plugin-import-jsx-pragma": "file:packages/babel-plugin-import-jsx-pragma", + "@wordpress/browserslist-config": "file:packages/browserslist-config", + "@wordpress/element": "file:packages/element", + "@wordpress/warning": "file:packages/warning", + "core-js": "^3.6.4" + } + }, "@wordpress/base-styles": { "version": "file:packages/base-styles", "dev": true @@ -16800,27 +13160,13 @@ "@wordpress/blob": { "version": "file:packages/blob", "requires": { - "@babel/runtime": "^7.11.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } + "@babel/runtime": "^7.12.5" } }, "@wordpress/block-directory": { "version": "file:packages/block-directory", "requires": { + "@babel/runtime": "^7.12.5", "@wordpress/a11y": "file:packages/a11y", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/block-editor": "file:packages/block-editor", @@ -16844,13 +13190,14 @@ "@wordpress/block-editor": { "version": "file:packages/block-editor", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/a11y": "file:packages/a11y", "@wordpress/blob": "file:packages/blob", "@wordpress/blocks": "file:packages/blocks", "@wordpress/components": "file:packages/components", "@wordpress/compose": "file:packages/compose", "@wordpress/data": "file:packages/data", + "@wordpress/data-controls": "file:packages/data-controls", "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/dom": "file:packages/dom", "@wordpress/element": "file:packages/element", @@ -16866,8 +13213,6 @@ "@wordpress/shortcode": "file:packages/shortcode", "@wordpress/token-list": "file:packages/token-list", "@wordpress/url": "file:packages/url", - "@wordpress/viewport": "file:packages/viewport", - "@wordpress/warning": "file:packages/warning", "@wordpress/wordcount": "file:packages/wordcount", "classnames": "^2.2.5", "css-mediaquery": "^0.1.2", @@ -16876,36 +13221,19 @@ "inherits": "^2.0.3", "lodash": "^4.17.19", "memize": "^1.1.0", - "react-autosize-textarea": "^3.0.2", + "react-autosize-textarea": "^7.1.0", "react-spring": "^8.0.19", - "react-transition-group": "^2.9.0", "reakit": "1.1.0", "redux-multi": "^0.1.12", - "refx": "^3.0.0", "rememo": "^3.0.0", "tinycolor2": "^1.4.1", "traverse": "^0.6.6" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/block-library": { "version": "file:packages/block-library", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/a11y": "file:packages/a11y", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/autop": "file:packages/autop", @@ -16942,42 +13270,12 @@ "react-easy-crop": "^3.0.0", "reakit": "1.1.0", "tinycolor2": "^1.4.1" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/block-serialization-default-parser": { "version": "file:packages/block-serialization-default-parser", "requires": { - "@babel/runtime": "^7.11.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } + "@babel/runtime": "^7.12.5" } }, "@wordpress/block-serialization-spec-parser": { @@ -16990,7 +13288,7 @@ "@wordpress/blocks": { "version": "file:packages/blocks", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/autop": "file:packages/autop", "@wordpress/blob": "file:packages/blob", "@wordpress/block-serialization-default-parser": "file:packages/block-serialization-default-parser", @@ -17011,22 +13309,7 @@ "showdown": "^1.9.1", "simple-html-tokenizer": "^0.5.7", "tinycolor2": "^1.4.1", - "uuid": "^7.0.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } + "uuid": "^8.3.0" } }, "@wordpress/browserslist-config": { @@ -17036,7 +13319,7 @@ "@wordpress/components": { "version": "file:packages/components", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@emotion/core": "^10.0.22", "@emotion/css": "^10.0.22", "@emotion/native": "^10.0.22", @@ -17071,57 +13354,32 @@ "reakit": "^1.1.0", "rememo": "^3.0.0", "tinycolor2": "^1.4.1", - "uuid": "^7.0.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } + "uuid": "^8.3.0" } }, "@wordpress/compose": { "version": "file:packages/compose", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", + "@wordpress/deprecated": "file:packages/deprecated", + "@wordpress/dom": "file:packages/dom", "@wordpress/element": "file:packages/element", "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", + "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/priority-queue": "file:packages/priority-queue", "clipboard": "^2.0.1", "lodash": "^4.17.19", + "memize": "^1.1.0", "mousetrap": "^1.6.5", + "react-merge-refs": "^1.0.0", "react-resize-aware": "^3.0.1", "use-memo-one": "^1.1.1" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/core-data": { "version": "file:packages/core-data", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/blocks": "file:packages/blocks", "@wordpress/data": "file:packages/data", @@ -17129,32 +13387,19 @@ "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/element": "file:packages/element", "@wordpress/i18n": "file:packages/i18n", - "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", - "@wordpress/url": "file:packages/url", - "equivalent-key-map": "^0.2.2", - "lodash": "^4.17.19", - "rememo": "^3.0.0" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } + "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", + "@wordpress/url": "file:packages/url", + "equivalent-key-map": "^0.2.2", + "lodash": "^4.17.19", + "rememo": "^3.0.0", + "uuid": "^8.3.0" } }, "@wordpress/create-block": { "version": "file:packages/create-block", "dev": true, "requires": { + "@wordpress/lazy-import": "file:packages/lazy-import", "chalk": "^4.0.0", "check-node-version": "^3.1.1", "commander": "^4.1.0", @@ -17177,7 +13422,7 @@ "@wordpress/data": { "version": "file:packages/data", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/compose": "file:packages/compose", "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/element": "file:packages/element", @@ -17191,59 +13436,23 @@ "redux": "^4.0.0", "turbo-combine-reducers": "^1.0.2", "use-memo-one": "^1.1.1" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/data-controls": { "version": "file:packages/data-controls", "requires": { + "@babel/runtime": "^7.12.5", "@wordpress/api-fetch": "file:packages/api-fetch", - "@wordpress/data": "file:packages/data" + "@wordpress/data": "file:packages/data", + "@wordpress/deprecated": "file:packages/deprecated" } }, "@wordpress/date": { "version": "file:packages/date", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "moment": "^2.22.1", "moment-timezone": "^0.5.31" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "moment-timezone": { - "version": "0.5.31", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", - "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", - "requires": { - "moment": ">= 2.9.0" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/dependency-extraction-webpack-plugin": { @@ -17251,37 +13460,21 @@ "dev": true, "requires": { "json2php": "^0.0.4", - "webpack": "^4.8.3", "webpack-sources": "^1.3.0" } }, "@wordpress/deprecated": { "version": "file:packages/deprecated", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/hooks": "file:packages/hooks" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/docgen": { "version": "file:packages/docgen", "dev": true, "requires": { - "@babel/core": "^7.11.0", + "@babel/core": "^7.12.9", "doctrine": "^2.1.0", "lodash": "^4.17.19", "mdast-util-inject": "1.1.0", @@ -17294,72 +13487,25 @@ "@wordpress/dom": { "version": "file:packages/dom", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "lodash": "^4.17.19" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/dom-ready": { "version": "file:packages/dom-ready", "requires": { - "@babel/runtime": "^7.11.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } + "@babel/runtime": "^7.12.5" } }, "@wordpress/e2e-test-utils": { "version": "file:packages/e2e-test-utils", "dev": true, "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/url": "file:packages/url", "lodash": "^4.17.19", "node-fetch": "^2.6.0" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - } } }, "@wordpress/e2e-tests": { @@ -17374,13 +13520,13 @@ "chalk": "^4.0.0", "expect-puppeteer": "^4.4.0", "lodash": "^4.17.19", - "uuid": "^7.0.2" + "uuid": "^8.3.0" } }, "@wordpress/edit-navigation": { "version": "file:packages/edit-navigation", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/block-editor": "file:packages/block-editor", "@wordpress/block-library": "file:packages/block-library", @@ -17403,28 +13549,13 @@ "classnames": "^2.2.5", "lodash": "^4.17.19", "rememo": "^3.0.0", - "uuid": "^7.0.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } + "uuid": "^8.3.0" } }, "@wordpress/edit-post": { "version": "file:packages/edit-post", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/a11y": "file:packages/a11y", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/block-editor": "file:packages/block-editor", @@ -17447,36 +13578,19 @@ "@wordpress/notices": "file:packages/notices", "@wordpress/plugins": "file:packages/plugins", "@wordpress/primitives": "file:packages/primitives", - "@wordpress/reusable-blocks": "file:packages/reusable-blocks", "@wordpress/url": "file:packages/url", "@wordpress/viewport": "file:packages/viewport", "@wordpress/warning": "file:packages/warning", "classnames": "^2.2.5", "lodash": "^4.17.19", "memize": "^1.1.0", - "refx": "^3.0.0", "rememo": "^3.0.0" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/edit-site": { "version": "file:packages/edit-site", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/a11y": "file:packages/a11y", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/block-editor": "file:packages/block-editor", @@ -17506,27 +13620,12 @@ "jszip": "^3.2.2", "lodash": "^4.17.19", "rememo": "^3.0.0" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/edit-widgets": { "version": "file:packages/edit-widgets", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/block-editor": "file:packages/block-editor", "@wordpress/block-library": "file:packages/block-library", @@ -17553,33 +13652,13 @@ "lodash": "^4.17.19", "reakit": "^1.1.0", "rememo": "^3.0.0", - "uuid": "^8.3.1" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - }, - "uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==" - } + "uuid": "^8.3.0" } }, "@wordpress/editor": { "version": "file:packages/editor", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/autop": "file:packages/autop", "@wordpress/blob": "file:packages/blob", @@ -17606,57 +13685,26 @@ "@wordpress/rich-text": "file:packages/rich-text", "@wordpress/server-side-render": "file:packages/server-side-render", "@wordpress/url": "file:packages/url", - "@wordpress/viewport": "file:packages/viewport", "@wordpress/wordcount": "file:packages/wordcount", "classnames": "^2.2.5", "lodash": "^4.17.19", "memize": "^1.1.0", - "react-autosize-textarea": "^3.0.2", + "react-autosize-textarea": "^7.1.0", "redux-optimist": "^1.0.0", "refx": "^3.0.0", "rememo": "^3.0.0" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/element": { "version": "file:packages/element", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@types/react": "^16.9.0", "@types/react-dom": "^16.9.0", "@wordpress/escape-html": "file:packages/escape-html", "lodash": "^4.17.19", "react": "^16.13.1", "react-dom": "^16.13.1" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/env": { @@ -17670,7 +13718,7 @@ "got": "^10.7.0", "inquirer": "^7.1.0", "js-yaml": "^3.13.1", - "nodegit": "^0.26.2", + "nodegit": "^0.27.0", "ora": "^4.0.2", "rimraf": "^3.0.2", "terminal-link": "^2.0.0", @@ -17680,22 +13728,7 @@ "@wordpress/escape-html": { "version": "file:packages/escape-html", "requires": { - "@babel/runtime": "^7.11.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } + "@babel/runtime": "^7.12.5" } }, "@wordpress/eslint-plugin": { @@ -17713,69 +13746,15 @@ "eslint-plugin-react": "^7.20.0", "eslint-plugin-react-hooks": "^4.0.4", "globals": "^12.0.0", - "prettier": "npm:wp-prettier@2.0.5", + "prettier": "npm:wp-prettier@2.2.1-beta-1", "requireindex": "^1.2.0" - }, - "dependencies": { - "cosmiconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", - "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } - }, - "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "parse-json": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.1.tgz", - "integrity": "sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", - "lines-and-columns": "^1.1.6" - } - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "yaml": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", - "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", - "dev": true - } } }, "@wordpress/format-library": { "version": "file:packages/format-library", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", + "@wordpress/a11y": "file:packages/a11y", "@wordpress/block-editor": "file:packages/block-editor", "@wordpress/components": "file:packages/components", "@wordpress/compose": "file:packages/compose", @@ -17789,188 +13768,68 @@ "@wordpress/rich-text": "file:packages/rich-text", "@wordpress/url": "file:packages/url", "lodash": "^4.17.19" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/hooks": { "version": "file:packages/hooks", "requires": { - "@babel/runtime": "^7.11.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } + "@babel/runtime": "^7.12.5" } }, "@wordpress/html-entities": { "version": "file:packages/html-entities", "requires": { - "@babel/runtime": "^7.11.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } + "@babel/runtime": "^7.12.5" } }, "@wordpress/i18n": { "version": "file:packages/i18n", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "gettext-parser": "^1.3.1", "lodash": "^4.17.19", "memize": "^1.1.0", "sprintf-js": "^1.1.1", "tannin": "^1.2.0" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/icons": { "version": "file:packages/icons", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/element": "file:packages/element", "@wordpress/primitives": "file:packages/primitives" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/interface": { "version": "file:packages/interface", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/components": "file:packages/components", "@wordpress/data": "file:packages/data", + "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/element": "file:packages/element", "@wordpress/i18n": "file:packages/i18n", "@wordpress/icons": "file:packages/icons", "@wordpress/plugins": "file:packages/plugins", + "@wordpress/viewport": "file:packages/viewport", "classnames": "^2.2.5", "lodash": "^4.17.19" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/is-shallow-equal": { "version": "file:packages/is-shallow-equal", "requires": { - "@babel/runtime": "^7.11.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } + "@babel/runtime": "^7.12.5" } }, "@wordpress/jest-console": { "version": "file:packages/jest-console", "dev": true, "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "jest-matcher-utils": "^25.3.0", "lodash": "^4.17.19" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - } } }, "@wordpress/jest-preset-default": { @@ -17989,75 +13848,28 @@ "version": "file:packages/jest-puppeteer-axe", "dev": true, "requires": { - "@babel/runtime": "^7.11.2", - "axe-puppeteer": "^1.1.0" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - } + "@axe-core/puppeteer": "^4.0.0", + "@babel/runtime": "^7.12.5" } }, "@wordpress/keyboard-shortcuts": { "version": "file:packages/keyboard-shortcuts", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/compose": "file:packages/compose", "@wordpress/data": "file:packages/data", "@wordpress/element": "file:packages/element", "@wordpress/keycodes": "file:packages/keycodes", "lodash": "^4.17.19", "rememo": "^3.0.0" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/keycodes": { "version": "file:packages/keycodes", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/i18n": "file:packages/i18n", "lodash": "^4.17.19" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/lazy-import": { @@ -18080,78 +13892,33 @@ "@wordpress/list-reusable-blocks": { "version": "file:packages/list-reusable-blocks", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/components": "file:packages/components", "@wordpress/compose": "file:packages/compose", "@wordpress/element": "file:packages/element", "@wordpress/i18n": "file:packages/i18n", "lodash": "^4.17.19" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/media-utils": { "version": "file:packages/media-utils", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/blob": "file:packages/blob", "@wordpress/element": "file:packages/element", "@wordpress/i18n": "file:packages/i18n", "lodash": "^4.17.19" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/notices": { "version": "file:packages/notices", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/a11y": "file:packages/a11y", "@wordpress/data": "file:packages/data", "lodash": "^4.17.19" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/npm-package-json-lint-config": { @@ -18161,7 +13928,7 @@ "@wordpress/nux": { "version": "file:packages/nux", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/components": "file:packages/components", "@wordpress/compose": "file:packages/compose", "@wordpress/data": "file:packages/data", @@ -18171,47 +13938,17 @@ "@wordpress/icons": "file:packages/icons", "lodash": "^4.17.19", "rememo": "^3.0.0" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/plugins": { "version": "file:packages/plugins", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/compose": "file:packages/compose", "@wordpress/element": "file:packages/element", "@wordpress/hooks": "file:packages/hooks", "@wordpress/icons": "file:packages/icons", "lodash": "^4.17.19" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/postcss-plugins-preset": { @@ -18238,45 +13975,15 @@ "@wordpress/primitives": { "version": "file:packages/primitives", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/element": "file:packages/element", "classnames": "^2.2.5" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/priority-queue": { "version": "file:packages/priority-queue", "requires": { - "@babel/runtime": "^7.11.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } + "@babel/runtime": "^7.12.5" } }, "@wordpress/project-management-automation": { @@ -18285,24 +13992,7 @@ "requires": { "@actions/core": "^1.0.0", "@actions/github": "^1.0.0", - "@babel/runtime": "^7.11.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - } + "@babel/runtime": "^7.12.5" } }, "@wordpress/react-native-aztec": { @@ -18320,7 +14010,7 @@ "@wordpress/react-native-editor": { "version": "file:packages/react-native-editor", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@react-native-community/blur": "3.6.0", "@react-native-community/masked-view": "git+https://github.com/wordpress-mobile/react-native-masked-view.git#f65a51a3320e58404d7f38d967bfd1f42b439ca9", "@react-native-community/slider": "git+https://github.com/wordpress-mobile/react-native-slider.git#d263ff16cdd9fb7352b354342522ff030f220f42", @@ -18366,14 +14056,6 @@ "react-native-video": "git+https://github.com/wordpress-mobile/react-native-video.git#1b964b107863351ed744fc104d7952bbec3e2d4f" }, "dependencies": { - "@babel/runtime": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.1.tgz", - "integrity": "sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, "jsc-android": { "version": "241213.1.0", "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-241213.1.0.tgz", @@ -18389,25 +14071,10 @@ "@wordpress/redux-routine": { "version": "file:packages/redux-routine", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "is-promise": "^4.0.0", "lodash": "^4.17.19", "rungen": "^0.3.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/reusable-blocks": { @@ -18423,54 +14090,26 @@ "@wordpress/i18n": "file:packages/i18n", "@wordpress/icons": "file:packages/icons", "@wordpress/notices": "file:packages/notices", + "@wordpress/url": "file:packages/url", "lodash": "^4.17.19" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/rich-text": { "version": "file:packages/rich-text", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/compose": "file:packages/compose", "@wordpress/data": "file:packages/data", "@wordpress/deprecated": "file:packages/deprecated", + "@wordpress/dom": "file:packages/dom", "@wordpress/element": "file:packages/element", "@wordpress/escape-html": "file:packages/escape-html", - "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", - "@wordpress/keycodes": "file:packages/keycodes", - "classnames": "^2.2.5", - "lodash": "^4.17.19", - "memize": "^1.1.0", - "rememo": "^3.0.0" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } + "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", + "@wordpress/keycodes": "file:packages/keycodes", + "classnames": "^2.2.5", + "lodash": "^4.17.19", + "memize": "^1.1.0", + "rememo": "^3.0.0" } }, "@wordpress/scripts": { @@ -18495,20 +14134,20 @@ "dir-glob": "^3.0.1", "eslint": "^7.1.0", "eslint-plugin-markdown": "^1.0.2", - "ignore-emit-webpack-plugin": "2.0.3", + "ignore-emit-webpack-plugin": "^2.0.6", "jest": "^25.3.0", "jest-puppeteer": "^4.4.0", "markdownlint": "^0.18.0", "markdownlint-cli": "^0.21.0", "mini-css-extract-plugin": "^0.9.0", "minimist": "^1.2.0", - "node-sass": "^4.13.1", "npm-package-json-lint": "^5.0.0", "postcss-loader": "^3.0.0", - "prettier": "npm:wp-prettier@2.0.5", + "prettier": "npm:wp-prettier@2.2.1-beta-1", "puppeteer": "npm:puppeteer-core@3.0.0", "read-pkg-up": "^1.0.1", "resolve-bin": "^0.4.0", + "sass": "^1.26.11", "sass-loader": "^8.0.2", "source-map-loader": "^0.2.4", "stylelint": "^13.6.0", @@ -18525,7 +14164,7 @@ "@wordpress/server-side-render": { "version": "file:packages/server-side-render", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/components": "file:packages/components", "@wordpress/data": "file:packages/data", @@ -18534,114 +14173,38 @@ "@wordpress/i18n": "file:packages/i18n", "@wordpress/url": "file:packages/url", "lodash": "^4.17.19" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/shortcode": { "version": "file:packages/shortcode", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "lodash": "^4.17.19", "memize": "^1.1.0" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/token-list": { "version": "file:packages/token-list", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "lodash": "^4.17.19" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/url": { "version": "file:packages/url", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "lodash": "^4.17.19", - "qs": "^6.5.2", "react-native-url-polyfill": "^1.1.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/viewport": { "version": "file:packages/viewport", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/compose": "file:packages/compose", "@wordpress/data": "file:packages/data", "lodash": "^4.17.19" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@wordpress/warning": { @@ -18650,23 +14213,8 @@ "@wordpress/wordcount": { "version": "file:packages/wordcount", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "lodash": "^4.17.19" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "@xtuc/ieee754": { @@ -18691,4168 +14239,1157 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@zkochan/cmd-shim/-/cmd-shim-3.1.0.tgz", "integrity": "sha512-o8l0+x7C7sMZU3v9GuJIAU10qQLtwR1dtRQIOmlNMtyaqhmpXOzx1HWiYoWfmmf9HHZoAkXpc9TM9PQYF9d4Jg==", - "dev": true, - "requires": { - "is-windows": "^1.0.0", - "mkdirp-promise": "^5.0.1", - "mz": "^2.5.0" - } - }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "abab": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", - "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", - "dev": true - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "absolute-path": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz", - "integrity": "sha1-p4di+9rftSl76ZsV01p4Wy8JW/c=" - }, - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" - } - }, - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", - "dev": true - }, - "acorn-globals": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", - "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", - "dev": true, - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - }, - "dependencies": { - "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", - "dev": true - } - } - }, - "acorn-jsx": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", - "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", - "dev": true - }, - "acorn-walk": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz", - "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==", - "dev": true - }, - "address": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz", - "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==", - "dev": true - }, - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "agentkeepalive": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", - "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", - "dev": true, - "requires": { - "humanize-ms": "^1.2.1" - } - }, - "aggregate-error": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.0.tgz", - "integrity": "sha512-yKD9kEoJIR+2IFqhMwayIBgheLYbB3PS2OBhWae1L/ODTd/JF/30cW0bc9TqzRL3k4U41Dieu3BF4I29p8xesA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^3.2.0" - } - }, - "airbnb-js-shims": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/airbnb-js-shims/-/airbnb-js-shims-2.2.1.tgz", - "integrity": "sha512-wJNXPH66U2xjgo1Zwyjf9EydvJ2Si94+vSdk6EERcBfB2VZkeltpqIats0cqIZMLCXP3zcyaUKGYQeIBT6XjsQ==", - "dev": true, - "requires": { - "array-includes": "^3.0.3", - "array.prototype.flat": "^1.2.1", - "array.prototype.flatmap": "^1.2.1", - "es5-shim": "^4.5.13", - "es6-shim": "^0.35.5", - "function.prototype.name": "^1.1.0", - "globalthis": "^1.0.0", - "object.entries": "^1.1.0", - "object.fromentries": "^2.0.0 || ^1.0.0", - "object.getownpropertydescriptors": "^2.0.3", - "object.values": "^1.1.0", - "promise.allsettled": "^1.0.0", - "promise.prototype.finally": "^3.1.0", - "string.prototype.matchall": "^4.0.0 || ^3.0.1", - "string.prototype.padend": "^3.0.0", - "string.prototype.padstart": "^3.0.0", - "symbol.prototype.description": "^1.0.0" - }, - "dependencies": { - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - }, - "dependencies": { - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - } - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true - }, - "object.entries": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz", - "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "has": "^1.0.3" - } - }, - "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - } - } - }, - "airbnb-prop-types": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.10.0.tgz", - "integrity": "sha512-M7kDqFO6kFNGV0fHPZaBx672m0jwbpCdbrtW2lcevCEuPB2sKCY3IPa030K/N1iJLEGwCNk4NSag65XBEulwhg==", - "requires": { - "array.prototype.find": "^2.0.4", - "function.prototype.name": "^1.1.0", - "has": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object.assign": "^4.1.0", - "object.entries": "^1.0.4", - "prop-types": "^15.6.1", - "prop-types-exact": "^1.1.2" - } - }, - "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true - }, - "ajv-keywords": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", - "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", - "dev": true - }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, - "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", - "dev": true - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true - }, - "ansi-align": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", - "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", - "dev": true, - "requires": { - "string-width": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "requires": { - "ansi-wrap": "^0.1.0" - } - }, - "ansi-cyan": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", - "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", - "requires": { - "ansi-wrap": "0.1.0" - } - }, - "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==" - }, - "ansi-fragments": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", - "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", - "requires": { - "colorette": "^1.0.7", - "slice-ansi": "^2.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - } + "dev": true, + "requires": { + "is-windows": "^1.0.0", + "mkdirp-promise": "^5.0.1", + "mz": "^2.5.0" } }, - "ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, "requires": { - "ansi-wrap": "0.1.0" + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" } }, - "ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "abab": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", + "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", "dev": true }, - "ansi-red": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", - "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "requires": { - "ansi-wrap": "0.1.0" + "event-target-shim": "^5.0.0" } }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "absolute-path": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz", + "integrity": "sha1-p4di+9rftSl76ZsV01p4Wy8JW/c=" }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", "requires": { - "color-convert": "^1.9.0" + "mime-types": "~2.1.18", + "negotiator": "0.6.1" } }, - "ansi-to-html": { - "version": "0.6.14", - "resolved": "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.6.14.tgz", - "integrity": "sha512-7ZslfB1+EnFSDO5Ju+ue5Y6It19DRnZXWv8jrGHgIlPna5Mh4jz7BV5jCbQneXNFurQcKoolaaAjHtgSBfOIuA==", + "acorn": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", + "dev": true + }, + "acorn-globals": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", + "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", "dev": true, "requires": { - "entities": "^1.1.2" + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" }, "dependencies": { - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "acorn-walk": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", + "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", "dev": true } } }, - "ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" - }, - "any-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", - "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", + "acorn-jsx": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", + "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", "dev": true }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "acorn-walk": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz", + "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==", "dev": true }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "app-root-dir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz", - "integrity": "sha1-OBh+wt6nV3//Az/8sSFyaS/24Rg=", + "address": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz", + "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==", "dev": true }, - "app-root-path": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.2.1.tgz", - "integrity": "sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==" - }, - "appium": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/appium/-/appium-1.17.1.tgz", - "integrity": "sha512-QdM7b0AAF/q79V8rauVZw24dGpU6tkLLHbbMG+yfNlsrsa8/xivOrbc3DM5Fmn8x4JjgUthHenIse7KRWCQy3w==", + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "dev": true, "requires": { - "@babel/runtime": "^7.6.0", - "appium-android-driver": "^4.20.0", - "appium-base-driver": "^5.0.0", - "appium-espresso-driver": "^1.0.0", - "appium-fake-driver": "^0.x", - "appium-flutter-driver": "^0", - "appium-ios-driver": "4.x", - "appium-mac-driver": "1.x", - "appium-support": "2.x", - "appium-tizen-driver": "^1.1.1-beta.4", - "appium-uiautomator2-driver": "^1.37.1", - "appium-windows-driver": "1.x", - "appium-xcuitest-driver": "^3.22.0", - "appium-youiengine-driver": "^1.2.0", - "argparse": "^1.0.10", - "async-lock": "^1.0.0", - "asyncbox": "2.x", - "bluebird": "3.x", - "continuation-local-storage": "3.x", - "dateformat": "^3.0.3", - "find-root": "^1.1.0", - "fsevents": "2.x", - "lodash": "^4.17.11", - "longjohn": "^0.2.12", - "npmlog": "4.x", - "request": "^2.81.0", - "request-promise": "4.x", - "semver": "^7.0.0", - "source-map-support": "0.x", - "teen_process": "1.x", - "winston": "3.x", - "word-wrap": "^1.2.3" - }, - "dependencies": { - "101": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/101/-/101-1.6.3.tgz", - "integrity": "sha512-4dmQ45yY0Dx24Qxp+zAsNLlMF6tteCyfVzgbulvSyC7tCyd3V8sW76sS0tHq8NpcbXfWTKasfyfzU1Kd86oKzw==", - "dev": true, - "requires": { - "clone": "^1.0.2", - "deep-eql": "^0.1.3", - "keypather": "^1.10.2" - } - }, - "@babel/polyfill": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.8.7.tgz", - "integrity": "sha512-LeSfP9bNZH2UOZgcGcZ0PIHUt1ZuHub1L3CVmEyqLxCeDLm4C5Gi8jRH8ZX2PNpDhQCo0z6y/+DIs2JlliXW8w==", - "dev": true, - "requires": { - "core-js": "^2.6.5", - "regenerator-runtime": "^0.13.4" - }, - "dependencies": { - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", - "dev": true - } - } - }, - "@babel/runtime": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz", - "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@dabh/diagnostics": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", - "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", - "requires": { - "colorspace": "1.1.x" - } - }, - "@jimp/bmp": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.9.5.tgz", - "integrity": "sha512-2cYdgXaNykuPe9sjm11Jihp5VomyWTWziIuDDB7xnxQtEz2HUR0bjXm2MJJOfU0TL52H+LS2JIKtAxcLPzp28w==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "bmp-js": "^0.1.0", - "core-js": "^3.4.1" - } - }, - "@jimp/core": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.9.5.tgz", - "integrity": "sha512-P1mlB9UOeI3IAQ4lGTmRBGw+F/mHWXd3tSyBskjL4E3YJ1eNK7WRrErUj/vUOvSBIryotu7nGo8vv8Q8JZ7/8w==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "any-base": "^1.1.0", - "buffer": "^5.2.0", - "core-js": "^3.4.1", - "exif-parser": "^0.1.12", - "file-type": "^9.0.0", - "load-bmfont": "^1.3.1", - "mkdirp": "0.5.1", - "phin": "^2.9.1", - "pixelmatch": "^4.0.2", - "tinycolor2": "^1.4.1" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - } - } - }, - "@jimp/custom": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.9.5.tgz", - "integrity": "sha512-FaR7M0oxqbd7ujBL5ryyllS+mEuMKbKaDsdb8Cpu9SAo80DBiasUrYFFD/45/aRa95aM5o8t4C4Pna2bx8t3Tg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/core": "^0.9.5", - "core-js": "^3.4.1" - } - }, - "@jimp/gif": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.9.5.tgz", - "integrity": "sha512-QxjLl15nIz/QTeNgLFUJIOMLIceMO2B/xLUWF1/WqaP7Su6SGasRS6JY8OZ9QnqJLMWkodoEJmL6DxwtoOtqdg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1", - "omggif": "^1.0.9" - } - }, - "@jimp/jpeg": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.9.5.tgz", - "integrity": "sha512-cBpXqmeegsLzf/mYk1WpYov2RH1W944re5P61/ag6AMWEMQ51BoBdgBy5JABZIELg2GQxpoG+g/KxUshRzeIAg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1", - "jpeg-js": "^0.3.4" - } - }, - "@jimp/plugin-blit": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.9.5.tgz", - "integrity": "sha512-VmV99HeCPOyliY/uEGOaKO9EcqDxSBzKDGC7emNCLFzlbK4uty4/cYMKGKTBiZR9AS1rEd63LxrDtbHKR8CsqQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-blur": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.9.5.tgz", - "integrity": "sha512-FnAEhMW9ZK8D6qCLDeMAloi4h7TCch9ZWFdonj49gwllpvLksBpnL9PTft4dFXCwZgOAq2apYwW7cwTAIfAw4A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-circle": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.10.3.tgz", - "integrity": "sha512-51GAPIVelqAcfuUpaM5JWJ0iWl4vEjNXB7p4P7SX5udugK5bxXUjO6KA2qgWmdpHuCKtoNgkzWU9fNSuYp7tCA==", - "requires": { - "@babel/runtime": "^7.7.2", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-color": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.9.5.tgz", - "integrity": "sha512-2aFE0tRdhAKCCgh+tFLsLPOSgrk3ttl2TtTP5FAXeKmzlLj7FZ/JKj0waaGWZKdJ+uDxsVpX3EhuK3CfukIyrg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1", - "tinycolor2": "^1.4.1" - } - }, - "@jimp/plugin-contain": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.9.5.tgz", - "integrity": "sha512-zhaCJnUqd8hhD8IXxbRALU6ZzCWWbQDulc8Tn8Hxnub0si7dlq/DxBQT7og6kCxswBj2zPBtRAHONEwLdt7Nfw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-cover": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.9.5.tgz", - "integrity": "sha512-rG7vtx7vV9mHCFR4YP9GzGEsaop0IkMidP3UFPULbDcBdEEkehEG7a0h2X4w/Nt07J3k8wVoXYTjrb/CXpWkaw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-crop": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.9.5.tgz", - "integrity": "sha512-yoScC43YhYlswTKyL4fmawGwF73HyuIRpp1R3mXa6qbMA9mjX9QiqNdAIMB3UMHeBcIgkOD/Zy1f90/skBMpxg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-displace": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.9.5.tgz", - "integrity": "sha512-nwfB72qNP8kNyBnlaY0vgJys7RUjvI61Qp3AMMbKKaRSsthCx7aeKU9Cyv+AHMfcVkkt3NdTmh7ScE+hkNFUhA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-dither": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.9.5.tgz", - "integrity": "sha512-Pp1ehm5Hon6LcttRG+d+x1UN1ww00P4cyBnMVRR3NMhIfgc0IjQgojik9ZXax3nVj7XkqXJJh8f5uxC1cvYUnA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-fisheye": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.10.3.tgz", - "integrity": "sha512-RRZb1wqe+xdocGcFtj2xHU7sF7xmEZmIa6BmrfSchjyA2b32TGPWKnP3qyj7p6LWEsXn+19hRYbjfyzyebPElQ==", - "requires": { - "@babel/runtime": "^7.7.2", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-flip": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.9.5.tgz", - "integrity": "sha512-rKbg8c9ePst3w2t1kxQt2H05/rUR5/pjjafhZ97s01pxH/SOJudy5d76nJGzRBYoaRnxpvDzpN+2+iA08wDY5Q==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-gaussian": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.9.5.tgz", - "integrity": "sha512-8HloHpVPgSsoWekslJ5uUPK2ddoLrGXQAVOyo3BT2pVgwbL317+r96NxPGKTxrY20fqex9SQrjx3kHeSWbysEA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-invert": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.9.5.tgz", - "integrity": "sha512-tqfMqQqsU4ulaif0Kk/BydqmG5UbjT67dmMjwnDL7rke+ypJ8tzq7j9QeZ9SDFB+PxUQcy/kPEw/R2Ys7HHi8A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-mask": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.9.5.tgz", - "integrity": "sha512-lIOrKb/VT1laDIA1H1nPOdtOB4TVhMRlxanXoEP8uKdE6a2goqZHXbKLn9itkm0MxtsTlT9KIXwzGxjCV38B3w==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-normalize": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.9.5.tgz", - "integrity": "sha512-gayxgPLDp2gynu2IacvdCtqw0bdcC2feUqYOBjTtCpAwIz1KP2Qd6qKjV1dAVGiLO9ESW5maMa0vIBiBkYOovg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-print": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.9.5.tgz", - "integrity": "sha512-/BUSyCfvVhuFdf+rBdH1wbuY8r9J0qhn4Icy7HqO58By7I+V7q7jayoeiLk+zEBsAXpCUbWiZG3KWNtZhLWeQg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1", - "load-bmfont": "^1.4.0" - } - }, - "@jimp/plugin-resize": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.9.5.tgz", - "integrity": "sha512-vIMleLPbEv0qTE1Mnc7mg5HSFc4l4FxlbDniVUvpi8ZMFa8IkigcTeAgXUKacevNL7uZ66MrnpQ49J3tNE28dQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-rotate": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.9.5.tgz", - "integrity": "sha512-BHlhwUruHNQkOpsfzTE2uuSfmkj5eiIDRSAC8whupUGGXNgS67tZJB6u0qDRIeSP/gWV5tGGwXQNMn3AahwR1Q==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-scale": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.9.5.tgz", - "integrity": "sha512-PDU8F77EPFTcLBVDcJtGUvPXA2acG4KqJMZauHwZLZxuiDEvt9qsDQm4aTKcN/ku8oWZjfGBSOamhx/QNUqV5Q==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-shadow": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.10.3.tgz", - "integrity": "sha512-/nkFXpt2zVcdP4ETdkAUL0fSzyrC5ZFxdcphbYBodqD7fXNqChS/Un1eD4xCXWEpW8cnG9dixZgQgStjywH0Mg==", - "requires": { - "@babel/runtime": "^7.7.2", - "core-js": "^3.4.1" - } - }, - "@jimp/plugin-threshold": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.10.3.tgz", - "integrity": "sha512-Dzh0Yq2wXP2SOnxcbbiyA4LJ2luwrdf1MghNIt9H+NX7B+IWw/N8qA2GuSm9n4BPGSLluuhdAWJqHcTiREriVA==", - "requires": { - "@babel/runtime": "^7.7.2", - "core-js": "^3.4.1" - } - }, - "@jimp/plugins": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.9.5.tgz", - "integrity": "sha512-3hvuXeRLj36ifpwE7I7g5Da9bKl/0y62t90ZN0hdQwhLBjRRF4u1e1JZpyu6EK98Bp+W/c8fJ2iuOsHadJOusg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/plugin-blit": "^0.9.5", - "@jimp/plugin-blur": "^0.9.5", - "@jimp/plugin-color": "^0.9.5", - "@jimp/plugin-contain": "^0.9.5", - "@jimp/plugin-cover": "^0.9.5", - "@jimp/plugin-crop": "^0.9.5", - "@jimp/plugin-displace": "^0.9.5", - "@jimp/plugin-dither": "^0.9.5", - "@jimp/plugin-flip": "^0.9.5", - "@jimp/plugin-gaussian": "^0.9.5", - "@jimp/plugin-invert": "^0.9.5", - "@jimp/plugin-mask": "^0.9.5", - "@jimp/plugin-normalize": "^0.9.5", - "@jimp/plugin-print": "^0.9.5", - "@jimp/plugin-resize": "^0.9.5", - "@jimp/plugin-rotate": "^0.9.5", - "@jimp/plugin-scale": "^0.9.5", - "core-js": "^3.4.1", - "timm": "^1.6.1" - } - }, - "@jimp/png": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.9.5.tgz", - "integrity": "sha512-0GPq/XixXcuWIA3gpMCUUj6rhxT78Hu9oDC9reaHUCcC/5cRTd5Eh7wLafZL8EfOZWV3mh2FZtWiY1xaNHHlBQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.9.5", - "core-js": "^3.4.1", - "pngjs": "^3.3.3" - }, - "dependencies": { - "pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", - "dev": true - } - } - }, - "@jimp/tiff": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.9.5.tgz", - "integrity": "sha512-EcRtiHsAQ9aygRRMWhGTVfitfHwllgt93GE1L8d/iwSlu3e3IIV38MDINdluQUQMU5jcFBcX6eyVVvsgCleGiQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "core-js": "^3.4.1", - "utif": "^2.0.1" - } - }, - "@jimp/types": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.9.5.tgz", - "integrity": "sha512-62inaxx8zy24WMP+bsg6ZmgsL49oyoGUIGcjDKzvyAY/O6opD+UMNlArhl0xvCCdzriQxbljtSv/8uyHxz4Xbw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/bmp": "^0.9.5", - "@jimp/gif": "^0.9.5", - "@jimp/jpeg": "^0.9.5", - "@jimp/png": "^0.9.5", - "@jimp/tiff": "^0.9.5", - "core-js": "^3.4.1", - "timm": "^1.6.1" - } - }, - "@jimp/utils": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.9.5.tgz", - "integrity": "sha512-W9vse4/1AYmOjtIVACoBMdc/2te1zcPURhMYNEyiezCU7hWMdj/Z1mwiWFq3AYCgOG8GPVx0ZQzrgqUfUxfTHQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "core-js": "^3.4.1" - } - }, - "@sindresorhus/is": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-3.0.0.tgz", - "integrity": "sha512-kqA5I6Yun7PBHk8WN9BBP1c7FfN2SrD05GuVSEYPqDb4nerv7HqYfgBfMIKmT/EuejURkJKLZuLyGKGs6WEG9w==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", - "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", - "dev": true, - "requires": { - "defer-to-connect": "^2.0.0" - } - }, - "@types/cacheable-request": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", - "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", - "dev": true, - "requires": { - "@types/http-cache-semantics": "*", - "@types/keyv": "*", - "@types/node": "*", - "@types/responselike": "*" - } - }, - "@types/caseless": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", - "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", - "dev": true - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "@types/http-cache-semantics": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", - "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", - "dev": true - }, - "@types/keyv": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", - "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "es6-promisify": "^5.0.0" + } + }, + "agentkeepalive": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", + "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", + "dev": true, + "requires": { + "humanize-ms": "^1.2.1" + } + }, + "aggregate-error": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.0.tgz", + "integrity": "sha512-yKD9kEoJIR+2IFqhMwayIBgheLYbB3PS2OBhWae1L/ODTd/JF/30cW0bc9TqzRL3k4U41Dieu3BF4I29p8xesA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^3.2.0" + } + }, + "airbnb-js-shims": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/airbnb-js-shims/-/airbnb-js-shims-2.2.1.tgz", + "integrity": "sha512-wJNXPH66U2xjgo1Zwyjf9EydvJ2Si94+vSdk6EERcBfB2VZkeltpqIats0cqIZMLCXP3zcyaUKGYQeIBT6XjsQ==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "array.prototype.flat": "^1.2.1", + "array.prototype.flatmap": "^1.2.1", + "es5-shim": "^4.5.13", + "es6-shim": "^0.35.5", + "function.prototype.name": "^1.1.0", + "globalthis": "^1.0.0", + "object.entries": "^1.1.0", + "object.fromentries": "^2.0.0 || ^1.0.0", + "object.getownpropertydescriptors": "^2.0.3", + "object.values": "^1.1.0", + "promise.allsettled": "^1.0.0", + "promise.prototype.finally": "^3.1.0", + "string.prototype.matchall": "^4.0.0 || ^3.0.1", + "string.prototype.padend": "^3.0.0", + "string.prototype.padstart": "^3.0.0", + "symbol.prototype.description": "^1.0.0" + }, + "dependencies": { + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "@types/node": "*" + "object-keys": "^1.0.12" } }, - "@types/node": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.0.tgz", - "integrity": "sha512-0ARSQootUG1RljH2HncpsY2TJBfGQIKOOi7kxzUY6z54ePu/ZD+wJA8zI2Q6v8rol2qpG/rvqsReco8zNMPvhQ==" - }, - "@types/request": { - "version": "2.48.5", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz", - "integrity": "sha512-/LO7xRVnL3DxJ1WkPGDQrp4VTV1reX9RkC85mJ+Qzykj2Bdw+mG15aAfDahc76HtknjzE16SX/Yddn6MxVbmGQ==", + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", "dev": true, "requires": { - "@types/caseless": "*", - "@types/node": "*", - "@types/tough-cookie": "*", - "form-data": "^2.5.0" + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" }, "dependencies": { - "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true } } }, - "@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { - "@types/node": "*" + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" } }, - "@types/tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==", + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", "dev": true }, - "@types/uuid": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", - "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==", + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", "dev": true }, - "@types/yauzl": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", - "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", - "dev": true, - "optional": true, - "requires": { - "@types/node": "*" - } - }, - "@wdio/config": { - "version": "5.18.4", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-5.18.4.tgz", - "integrity": "sha512-HQugjG+BABDYG/1dPR6KA+IQilsg1MSQ/NVIg8R6I8ER9MA2JNIoaxvXZ+CnDfgY/QpyIHEeqJhfgw8GElaPdw==", + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { - "@wdio/logger": "5.16.10", - "deepmerge": "^4.0.0", - "glob": "^7.1.2" + "has-symbols": "^1.0.1" } }, - "@wdio/logger": { - "version": "5.16.10", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-5.16.10.tgz", - "integrity": "sha512-hRKhxgd9uB48Dtj2xe2ckxU4KwI/RO8IwguySuaI2SLFj6EDbdonwzpVkq111/fjBuq7R1NauAaNcm3AMEbIFA==", + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "requires": { - "chalk": "^3.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } + "has-symbols": "^1.0.1" } }, - "@wdio/protocols": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-5.19.0.tgz", - "integrity": "sha512-6DTSZKJd/UwtiTuQ2GgfQV/LTnjVmAn6uNzUz2j5cdFxCI63vaRQ4AnaPFjjT3VuN+rJXnmRF8FKoT02jBCQrg==", + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", "dev": true }, - "@wdio/repl": { - "version": "5.18.6", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-5.18.6.tgz", - "integrity": "sha512-z9UPBk/Uee0l9g0ijnOatU3WP7TcpIyNtRj9AGsJVbYZFwqMWBqPkO4nblldyNQIuqdgXAPsDo8lPGDno12/oA==", - "dev": true, - "requires": { - "@wdio/utils": "5.18.6" - } - }, - "@wdio/utils": { - "version": "5.18.6", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-5.18.6.tgz", - "integrity": "sha512-OVdK7P9Gne9tR6dl1GEKucwX4mtS47F26g4lH8r0HURvMegZLGtcchI1cqF6hjK7EpP737b+C3q4ooZSBdH9XQ==", - "dev": true, - "requires": { - "@wdio/logger": "5.16.10", - "deepmerge": "^4.0.0" - } - }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "object.entries": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz", + "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", "dev": true, "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "has": "^1.0.3" } }, - "adbkit-apkreader": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/adbkit-apkreader/-/adbkit-apkreader-3.2.0.tgz", - "integrity": "sha512-QwsxPYCqWSmCAiW/A4gq0eytb4jtZc7WNbECIhLCRfGEB38oXzIV/YkTpkOTQFKSg3S4Svb6y///qOUH7UrWWw==", + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", "dev": true, "requires": { - "bluebird": "^3.4.7", - "debug": "~4.1.1", - "yauzl": "^2.7.0" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" } }, - "agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==" - }, - "aggregate-error": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", - "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", "dev": true, "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" } }, - "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", "dev": true, "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "dependencies": { - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - } + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" } - }, - "ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + } + } + }, + "airbnb-prop-types": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.10.0.tgz", + "integrity": "sha512-M7kDqFO6kFNGV0fHPZaBx672m0jwbpCdbrtW2lcevCEuPB2sKCY3IPa030K/N1iJLEGwCNk4NSag65XBEulwhg==", + "requires": { + "array.prototype.find": "^2.0.4", + "function.prototype.name": "^1.1.0", + "has": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object.assign": "^4.1.0", + "object.entries": "^1.0.4", + "prop-types": "^15.6.1", + "prop-types-exact": "^1.1.2" + } + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true + }, + "ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", + "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "dev": true + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "ansi-wrap": "0.1.0" + "is-buffer": "^1.1.5" } - }, + } + } + }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", + "dev": true + }, + "ansi-align": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", + "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "dev": true, + "requires": { + "string-width": "^3.0.0" + }, + "dependencies": { "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", - "dev": true - }, - "any-base": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", - "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, - "appium-adb": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/appium-adb/-/appium-adb-7.26.0.tgz", - "integrity": "sha512-EWVj2kaPmbnGTrr9sX+XqMmYKKdFlo94M106fEfLbb41PxJTn/e6bwJ6qwXjGMpJhlpgbjUwFGhnUkaut6lD+w==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "adbkit-apkreader": "^3.1.2", - "appium-support": "^2.37.0", - "async-lock": "^1.0.0", - "asyncbox": "^2.3.1", - "bluebird": "^3.4.7", - "lodash": "^4.0.0", - "lru-cache": "^5.0.0", - "semver": "^7.0.0", - "shell-quote": "^1.6.1", - "source-map-support": "^0.5.5", - "teen_process": "^1.11.0", - "utf7": "^1.0.2" - } - }, - "appium-android-driver": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/appium-android-driver/-/appium-android-driver-4.26.1.tgz", - "integrity": "sha512-vczjhG0DOToBDV/++0iMdx4wXvTD/CaICH4F9IbTPa0APOTSGsL+k113vmD2Alsquvq/F/Kj5MFIq2DlpPjhgw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "appium-adb": "^7.22.0", - "appium-base-driver": "^5.1.0", - "appium-chromedriver": "^4.13.0", - "appium-support": "^2.37.0", - "asyncbox": "^2.0.4", - "bluebird": "^3.4.7", - "io.appium.settings": "^3.1.0", - "jimp": "^0.9.3", - "lodash": "^4.17.4", - "moment": "^2.24.0", - "moment-timezone": "^0.5.26", - "portfinder": "^1.0.6", - "portscanner": "2.2.0", - "semver": "^7.0.0", - "shared-preferences-builder": "^0.0.4", - "shell-quote": "^1.6.1", - "source-map-support": "^0.5.5", - "teen_process": "^1.9.0", - "ws": "^7.0.0", - "yargs": "^15.0.1" - } - }, - "appium-base-driver": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/appium-base-driver/-/appium-base-driver-5.3.1.tgz", - "integrity": "sha512-IQpmGI3+CzBzbW34x1eGRIkOuMYqJ1uHvJwmThFJBLoaYZNDPRlO37g0np2TjESwOBYpD1DSEWLxzkKQusU1rA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "appium-support": "^2.39.0", - "async-lock": "^1.0.0", - "asyncbox": "^2.3.1", - "bluebird": "^3.5.3", - "body-parser": "^1.18.2", - "colors": "^1.1.2", - "es6-error": "^4.1.1", - "express": "^4.16.2", - "http-status-codes": "^1.3.0", - "lodash": "^4.0.0", - "lru-cache": "^5.0.0", - "method-override": "^3.0.0", - "morgan": "^1.9.0", - "request": "^2.83.0", - "request-promise": "^4.2.2", - "sanitize-filename": "^1.6.1", - "serve-favicon": "^2.4.5", - "source-map-support": "^0.5.5", - "uuid-js": "^0.7.5", - "validate.js": "^0.13.0", - "webdriverio": "^5.10.9", - "ws": "^7.0.0" - } - }, - "appium-chromedriver": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/appium-chromedriver/-/appium-chromedriver-4.21.0.tgz", - "integrity": "sha512-93LmpeC7sCqjHzMhk+1JR8VM9rNwLJ/mNWjjG26ngqfSY/xNn5fzq9ff+TUKXYVlyPqE64ODi+5UifKUZ9QZ7Q==", + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "@babel/runtime": "^7.0.0", - "appium-base-driver": "^5.0.0", - "appium-support": "^2.39.0", - "asyncbox": "^2.0.2", - "bluebird": "^3.5.1", - "compare-versions": "^3.4.0", - "fancy-log": "^1.3.2", - "lodash": "^4.17.4", - "request": "^2.57.0", - "request-promise": "^4.2.2", - "semver": "^7.0.0", - "source-map-support": "^0.5.5", - "teen_process": "^1.15.0", - "xmldom": "^0.2.0", - "xpath": "^0.0.27" - }, - "dependencies": { - "xmldom": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.2.1.tgz", - "integrity": "sha512-kXXiYvmblIgEemGeB75y97FyaZavx6SQhGppLw5TKWAD2Wd0KAly0g23eVLh17YcpxZpnFym1Qk/eaRjy1APPg==", - "dev": true - } + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" } }, - "appium-espresso-driver": { - "version": "1.23.2", - "resolved": "https://registry.npmjs.org/appium-espresso-driver/-/appium-espresso-driver-1.23.2.tgz", - "integrity": "sha512-NY5AXM1Mo5iFOih8iXinLkuT00w3P+k0qWtkBphVc1z1u0x9dBW6CMdH9a1W25g3u/4yJZIAAdY7A4dyrCIpyw==", + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "@babel/runtime": "^7.4.3", - "appium-adb": "^7.19.1", - "appium-android-driver": "^4.25.3", - "appium-base-driver": "^5.0.0", - "appium-support": "^2.20.0", - "asyncbox": "^2.3.1", - "bluebird": "^3.5.0", - "lodash": "^4.17.11", - "portscanner": "^2.1.1", - "request": "^2.88.0", - "request-promise": "^4.2.1", - "source-map-support": "^0.5.8", - "validate.js": "^0.13.0", - "yargs": "^15.0.1" + "ansi-regex": "^4.1.0" } + } + } + }, + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-escapes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==" + }, + "ansi-fragments": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", + "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", + "requires": { + "colorette": "^1.0.7", + "slice-ansi": "^2.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" }, - "appium-fake-driver": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/appium-fake-driver/-/appium-fake-driver-0.9.0.tgz", - "integrity": "sha512-MwCqt1vz+bRidl9NRNcToJZostKEJQAzBizh9QyZ+5n3/N+OTBYCp5AD/pJw/MjF4Wl1u1Jhu2jFxsu+XaQpqA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "appium-base-driver": "^5.0.0", - "appium-support": "^2.11.1", - "asyncbox": "^2.3.2", - "bluebird": "^3.5.1", - "lodash": "^4.17.4", - "source-map-support": "^0.5.5", - "xmldom": "^0.1.19", - "xpath": "0.0.27", - "yargs": "^15.0.1" - }, - "dependencies": { - "xmldom": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.3.0.tgz", - "integrity": "sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g==", - "dev": true - } + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" } }, - "appium-flutter-driver": { - "version": "0.0.23", - "resolved": "https://registry.npmjs.org/appium-flutter-driver/-/appium-flutter-driver-0.0.23.tgz", - "integrity": "sha512-yRZwfioaMpE2s9Av8pgv6hQkArsQ36KkrEgHaOk5uRRuTXbHSg0OB51Bm8xKdXvWYmA4DI6Oz4xWSTNjfdrJhw==", - "dev": true, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "requires": { - "appium-base-driver": "^5.0.0", - "appium-uiautomator2-driver": "^1.35.1", - "appium-xcuitest-driver": "^3.0.0", - "rpc-websockets": "^4.5.1" - }, - "dependencies": { - "@jimp/bmp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.14.0.tgz", - "integrity": "sha512-5RkX6tSS7K3K3xNEb2ygPuvyL9whjanhoaB/WmmXlJS6ub4DjTqrapu8j4qnIWmO4YYtFeTbDTXV6v9P1yMA5A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "bmp-js": "^0.1.0" - } - }, - "@jimp/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.14.0.tgz", - "integrity": "sha512-S62FcKdtLtj3yWsGfJRdFXSutjvHg7aQNiFogMbwq19RP4XJWqS2nOphu7ScB8KrSlyy5nPF2hkWNhLRLyD82w==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "any-base": "^1.1.0", - "buffer": "^5.2.0", - "exif-parser": "^0.1.12", - "file-type": "^9.0.0", - "load-bmfont": "^1.3.1", - "mkdirp": "^0.5.1", - "phin": "^2.9.1", - "pixelmatch": "^4.0.2", - "tinycolor2": "^1.4.1" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "@jimp/custom": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.14.0.tgz", - "integrity": "sha512-kQJMeH87+kWJdVw8F9GQhtsageqqxrvzg7yyOw3Tx/s7v5RToe8RnKyMM+kVtBJtNAG+Xyv/z01uYQ2jiZ3GwA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/core": "^0.14.0" - } - }, - "@jimp/gif": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.14.0.tgz", - "integrity": "sha512-DHjoOSfCaCz72+oGGEh8qH0zE6pUBaBxPxxmpYJjkNyDZP7RkbBkZJScIYeQ7BmJxmGN4/dZn+MxamoQlr+UYg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "gifwrap": "^0.9.2", - "omggif": "^1.0.9" - } - }, - "@jimp/jpeg": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.14.0.tgz", - "integrity": "sha512-561neGbr+87S/YVQYnZSTyjWTHBm9F6F1obYHiyU3wVmF+1CLbxY3FQzt4YolwyQHIBv36Bo0PY2KkkU8BEeeQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "jpeg-js": "^0.4.0" - } - }, - "@jimp/plugin-blit": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.14.0.tgz", - "integrity": "sha512-YoYOrnVHeX3InfgbJawAU601iTZMwEBZkyqcP1V/S33Qnz9uzH1Uj1NtC6fNgWzvX6I4XbCWwtr4RrGFb5CFrw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-blur": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.14.0.tgz", - "integrity": "sha512-9WhZcofLrT0hgI7t0chf7iBQZib//0gJh9WcQMUt5+Q1Bk04dWs8vTgLNj61GBqZXgHSPzE4OpCrrLDBG8zlhQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-circle": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.14.0.tgz", - "integrity": "sha512-o5L+wf6QA44tvTum5HeLyLSc5eVfIUd5ZDVi5iRfO4o6GT/zux9AxuTSkKwnjhsG8bn1dDmywAOQGAx7BjrQVA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-color": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.14.0.tgz", - "integrity": "sha512-JJz512SAILYV0M5LzBb9sbOm/XEj2fGElMiHAxb7aLI6jx+n0agxtHpfpV/AePTLm1vzzDxx6AJxXbKv355hBQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "tinycolor2": "^1.4.1" - } - }, - "@jimp/plugin-contain": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.14.0.tgz", - "integrity": "sha512-RX2q233lGyaxiMY6kAgnm9ScmEkNSof0hdlaJAVDS1OgXphGAYAeSIAwzESZN4x3ORaWvkFefeVH9O9/698Evg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-cover": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.14.0.tgz", - "integrity": "sha512-0P/5XhzWES4uMdvbi3beUgfvhn4YuQ/ny8ijs5kkYIw6K8mHcl820HahuGpwWMx56DJLHRl1hFhJwo9CeTRJtQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-crop": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.14.0.tgz", - "integrity": "sha512-Ojtih+XIe6/XSGtpWtbAXBozhCdsDMmy+THUJAGu2x7ZgKrMS0JotN+vN2YC3nwDpYkM+yOJImQeptSfZb2Sug==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-displace": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.14.0.tgz", - "integrity": "sha512-c75uQUzMgrHa8vegkgUvgRL/PRvD7paFbFJvzW0Ugs8Wl+CDMGIPYQ3j7IVaQkIS+cAxv+NJ3TIRBQyBrfVEOg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-dither": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.14.0.tgz", - "integrity": "sha512-g8SJqFLyYexXQQsoh4dc1VP87TwyOgeTElBcxSXX2LaaMZezypmxQfLTzOFzZoK8m39NuaoH21Ou1Ftsq7LzVQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-fisheye": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.14.0.tgz", - "integrity": "sha512-BFfUZ64EikCaABhCA6mR3bsltWhPpS321jpeIQfJyrILdpFsZ/OccNwCgpW1XlbldDHIoNtXTDGn3E+vCE7vDg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-flip": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.14.0.tgz", - "integrity": "sha512-WtL1hj6ryqHhApih+9qZQYA6Ye8a4HAmdTzLbYdTMrrrSUgIzFdiZsD0WeDHpgS/+QMsWwF+NFmTZmxNWqKfXw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-gaussian": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.14.0.tgz", - "integrity": "sha512-uaLwQ0XAQoydDlF9tlfc7iD9drYPriFe+jgYnWm8fbw5cN+eOIcnneEX9XCOOzwgLPkNCxGox6Kxjn8zY6GxtQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-invert": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.14.0.tgz", - "integrity": "sha512-UaQW9X9vx8orQXYSjT5VcITkJPwDaHwrBbxxPoDG+F/Zgv4oV9fP+udDD6qmkgI9taU+44Fy+zm/J/gGcMWrdg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-mask": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.14.0.tgz", - "integrity": "sha512-tdiGM69OBaKtSPfYSQeflzFhEpoRZ+BvKfDEoivyTjauynbjpRiwB1CaiS8En1INTDwzLXTT0Be9SpI3LkJoEA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-normalize": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.14.0.tgz", - "integrity": "sha512-AfY8sqlsbbdVwFGcyIPy5JH/7fnBzlmuweb+Qtx2vn29okq6+HelLjw2b+VT2btgGUmWWHGEHd86oRGSoWGyEQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-print": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.14.0.tgz", - "integrity": "sha512-MwP3sH+VS5AhhSTXk7pui+tEJFsxnTKFY3TraFJb8WFbA2Vo2qsRCZseEGwpTLhENB7p/JSsLvWoSSbpmxhFAQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "load-bmfont": "^1.4.0" - } - }, - "@jimp/plugin-resize": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.14.0.tgz", - "integrity": "sha512-qFeMOyXE/Bk6QXN0GQo89+CB2dQcXqoxUcDb2Ah8wdYlKqpi53skABkgVy5pW3EpiprDnzNDboMltdvDslNgLQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-rotate": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.14.0.tgz", - "integrity": "sha512-aGaicts44bvpTcq5Dtf93/8TZFu5pMo/61lWWnYmwJJU1RqtQlxbCLEQpMyRhKDNSfPbuP8nyGmaqXlM/82J0Q==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-scale": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.14.0.tgz", - "integrity": "sha512-ZcJk0hxY5ZKZDDwflqQNHEGRblgaR+piePZm7dPwPUOSeYEH31P0AwZ1ziceR74zd8N80M0TMft+e3Td6KGBHw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-shadow": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.14.0.tgz", - "integrity": "sha512-p2igcEr/iGrLiTu0YePNHyby0WYAXM14c5cECZIVnq/UTOOIQ7xIcWZJ1lRbAEPxVVXPN1UibhZAbr3HAb5BjQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-threshold": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.14.0.tgz", - "integrity": "sha512-N4BlDgm/FoOMV/DQM2rSpzsgqAzkP0DXkWZoqaQrlRxQBo4zizQLzhEL00T/YCCMKnddzgEhnByaocgaaa0fKw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugins": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.14.0.tgz", - "integrity": "sha512-vDO3XT/YQlFlFLq5TqNjQkISqjBHT8VMhpWhAfJVwuXIpilxz5Glu4IDLK6jp4IjPR6Yg2WO8TmRY/HI8vLrOw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/plugin-blit": "^0.14.0", - "@jimp/plugin-blur": "^0.14.0", - "@jimp/plugin-circle": "^0.14.0", - "@jimp/plugin-color": "^0.14.0", - "@jimp/plugin-contain": "^0.14.0", - "@jimp/plugin-cover": "^0.14.0", - "@jimp/plugin-crop": "^0.14.0", - "@jimp/plugin-displace": "^0.14.0", - "@jimp/plugin-dither": "^0.14.0", - "@jimp/plugin-fisheye": "^0.14.0", - "@jimp/plugin-flip": "^0.14.0", - "@jimp/plugin-gaussian": "^0.14.0", - "@jimp/plugin-invert": "^0.14.0", - "@jimp/plugin-mask": "^0.14.0", - "@jimp/plugin-normalize": "^0.14.0", - "@jimp/plugin-print": "^0.14.0", - "@jimp/plugin-resize": "^0.14.0", - "@jimp/plugin-rotate": "^0.14.0", - "@jimp/plugin-scale": "^0.14.0", - "@jimp/plugin-shadow": "^0.14.0", - "@jimp/plugin-threshold": "^0.14.0", - "timm": "^1.6.1" - } - }, - "@jimp/png": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.14.0.tgz", - "integrity": "sha512-0RV/mEIDOrPCcNfXSPmPBqqSZYwGADNRVUTyMt47RuZh7sugbYdv/uvKmQSiqRdR0L1sfbCBMWUEa5G/8MSbdA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "pngjs": "^3.3.3" - }, - "dependencies": { - "pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", - "dev": true - } - } - }, - "@jimp/tiff": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.14.0.tgz", - "integrity": "sha512-zBYDTlutc7j88G/7FBCn3kmQwWr0rmm1e0FKB4C3uJ5oYfT8645lftUsvosKVUEfkdmOaMAnhrf4ekaHcb5gQw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "utif": "^2.0.1" - } - }, - "@jimp/types": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.14.0.tgz", - "integrity": "sha512-hx3cXAW1KZm+b+XCrY3LXtdWy2U+hNtq0rPyJ7NuXCjU7lZR3vIkpz1DLJ3yDdS70hTi5QDXY3Cd9kd6DtloHQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/bmp": "^0.14.0", - "@jimp/gif": "^0.14.0", - "@jimp/jpeg": "^0.14.0", - "@jimp/png": "^0.14.0", - "@jimp/tiff": "^0.14.0", - "timm": "^1.6.1" - } - }, - "@jimp/utils": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.14.0.tgz", - "integrity": "sha512-MY5KFYUru0y74IsgM/9asDwb3ERxWxXEu3CRCZEvE7DtT86y1bR1XgtlSliMrptjz4qbivNGMQSvUBpEFJDp1A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "regenerator-runtime": "^0.13.3" - } - }, - "@wdio/config": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-6.8.0.tgz", - "integrity": "sha512-q71VJpa50iz9Bq0QoZIT+egZIAQGiv6mRcmqub8/R318XL35Y6NKHJIOyHl8xVmHwitYy//fawZou2nYjU66KQ==", - "dev": true, - "requires": { - "@wdio/logger": "6.8.0", - "deepmerge": "^4.0.0", - "glob": "^7.1.2" - } - }, - "@wdio/logger": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-6.8.0.tgz", - "integrity": "sha512-IvRnp2gTU1z6L+snMrKLrRDqYFq9yzcqXp7i6+Q/bxewxkgcpitm4hSs+13KS4fmbeBmhT5UeUeumnTZBYkhBQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - } - }, - "@wdio/protocols": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-6.8.0.tgz", - "integrity": "sha512-A9k3DYBxt220SK57LlALscHd/4KUa6kzJdc4UJ84Dyylymmjhs3Uau9WL37yyMMd6Y/5sSfUNRrAUEDZnmOzyQ==", - "dev": true - }, - "@wdio/repl": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-6.8.0.tgz", - "integrity": "sha512-unFnItXq6+V8JNfAtPtuEza047r2dLdcFXPN4exq7+O/kPJTzsTGOAQTlSLPJGMrfy5axTk90KOl08gpJvzjOA==", - "dev": true, - "requires": { - "@wdio/utils": "6.8.0" - } - }, - "@wdio/utils": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-6.8.0.tgz", - "integrity": "sha512-2vGwkaqP2e876o3NDTWz021aLTBrbZfCLHETuS+e/J0IXMR3FQ8et01BY/bjwyz6EP1I3vVtP2ZVC1dV2yIIVQ==", - "dev": true, - "requires": { - "@wdio/logger": "6.8.0" - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "appium-base-driver": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/appium-base-driver/-/appium-base-driver-5.8.1.tgz", - "integrity": "sha512-k5ybExgP0kJx7vsR0Y8GeEay+Vr0yCs0muzYtdrIDbqOCz5bFX0skyZubSSsm/lOE4KvgHhm4cRwWJM0DlHvRA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "appium-support": "^2.46.0", - "async-lock": "^1.0.0", - "asyncbox": "^2.3.1", - "axios": "^0.19.2", - "bluebird": "^3.5.3", - "body-parser": "^1.18.2", - "colors": "^1.1.2", - "es6-error": "^4.1.1", - "express": "^4.16.2", - "http-status-codes": "^1.3.0", - "lodash": "^4.0.0", - "lru-cache": "^5.0.0", - "method-override": "^3.0.0", - "morgan": "^1.9.0", - "request": "^2.88.2", - "request-promise": "^4.2.5", - "serve-favicon": "^2.4.5", - "source-map-support": "^0.5.5", - "validate.js": "^0.13.0", - "webdriverio": "^6.0.2", - "ws": "^7.0.0" - } - }, - "appium-support": { - "version": "2.49.0", - "resolved": "https://registry.npmjs.org/appium-support/-/appium-support-2.49.0.tgz", - "integrity": "sha512-PtUsfpCjdGqZTTuRyH5U9iOGq9SpEVHg0H6/2HrazkX6ZvSQ+kIVYHTAGIXVeBJecfN9/syqgMmEekVgD15BGA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "archiver": "^5.0.0", - "axios": "^0.20.0", - "base64-stream": "^1.0.0", - "bluebird": "^3.5.1", - "bplist-creator": "^0", - "bplist-parser": "^0.2", - "form-data": "^3.0.0", - "get-stream": "^6.0.0", - "glob": "^7.1.2", - "jimp": "^0.14.0", - "jsftp": "^2.1.2", - "klaw": "^3.0.0", - "lockfile": "^1.0.4", - "lodash": "^4.2.1", - "mjpeg-server": "^0.3.0", - "mkdirp": "^1.0.0", - "moment": "^2.24.0", - "mv": "^2.1.1", - "ncp": "^2.0.0", - "npmlog": "^4.1.2", - "plist": "^3.0.1", - "pluralize": "^8.0.0", - "pngjs": "^5.0.0", - "rimraf": "^3.0.0", - "sanitize-filename": "^1.6.1", - "semver": "^7.0.0", - "shell-quote": "^1.7.2", - "source-map-support": "^0.5.5", - "teen_process": "^1.5.1", - "uuid": "^8.0.0", - "which": "^2.0.0", - "yauzl": "^2.7.0" - }, - "dependencies": { - "axios": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", - "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", - "dev": true, - "requires": { - "follow-redirects": "^1.10.0" - } - } - } - }, - "archiver": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.0.2.tgz", - "integrity": "sha512-Tq3yV/T4wxBsD2Wign8W9VQKhaUxzzRmjEiSoOK0SLqPgDP/N1TKdYyBeIEu56T4I9iO4fKTTR0mN9NWkBA0sg==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "async": "^3.2.0", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", - "tar-stream": "^2.1.4", - "zip-stream": "^4.0.0" - } - }, - "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dev": true, - "requires": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } - } - }, - "async": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", - "dev": true - }, - "bl": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "compress-commons": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.0.1.tgz", - "integrity": "sha512-xZm9o6iikekkI0GnXCmAl3LQGZj5TBDj0zLowsqi7tJtEa3FMGSEcHcqrSJIrOAk1UG/NBbDn/F1q+MG/p/EsA==", - "dev": true, - "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.0", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - } - }, - "crc32-stream": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.0.tgz", - "integrity": "sha512-tyMw2IeUX6t9jhgXI6um0eKfWq4EIDpfv5m7GX4Jzp7eVelQ360xd8EPXJhp2mHwLQIkqlnMLjzqSZI3a+0wRw==", - "dev": true, - "requires": { - "crc": "^3.4.4", - "readable-stream": "^3.4.0" - } - }, - "devtools": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-6.8.0.tgz", - "integrity": "sha512-DaXkQP312NclQPzSDHD3wvenglkxUDKiQwjlEqFvOXc+mc4o10WljVUx6B3Q0Ig7YmgBHsKREcNFYAwXTnzT7Q==", - "dev": true, - "requires": { - "@types/puppeteer-core": "^2.0.0", - "@types/ua-parser-js": "^0.7.33", - "@types/uuid": "^8.3.0", - "@wdio/config": "6.8.0", - "@wdio/logger": "6.8.0", - "@wdio/protocols": "6.8.0", - "@wdio/utils": "6.8.0", - "chrome-launcher": "^0.13.1", - "edge-paths": "^2.1.0", - "puppeteer-core": "^5.1.0", - "ua-parser-js": "^0.7.21", - "uuid": "^8.0.0" - } - }, - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - } - } - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "follow-redirects": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", - "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", - "dev": true - }, - "form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", - "dev": true - }, - "jimp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.14.0.tgz", - "integrity": "sha512-8BXU+J8+SPmwwyq9ELihpSV4dWPTiOKBWCEgtkbnxxAVMjXdf3yGmyaLSshBfXc8sP/JQ9OZj5R8nZzz2wPXgA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/custom": "^0.14.0", - "@jimp/plugins": "^0.14.0", - "@jimp/types": "^0.14.0", - "regenerator-runtime": "^0.13.3" - } - }, - "jpeg-js": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.2.tgz", - "integrity": "sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw==", - "dev": true - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "pngjs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", - "dev": true - }, - "puppeteer-core": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-5.5.0.tgz", - "integrity": "sha512-tlA+1n+ziW/Db03hVV+bAecDKse8ihFRXYiEypBe9IlLRvOCzYFG6qrCMBYK34HO/Q/Ecjc+tvkHRAfLVH+NgQ==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.818844", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^4.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.0.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "resq": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/resq/-/resq-1.9.2.tgz", - "integrity": "sha512-Y+fprJ9wQY64gh+vJRNatiG61G+9XD5jJe4kI/Rqw6gmOa5ihZvgrxZVydqyM96xj75jwaRCPVYPU3RwsEk6ug==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1" - } - }, - "rgb2hex": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.0.tgz", - "integrity": "sha512-cHdNTwmTMPu/TpP1bJfdApd6MbD+Kzi4GNnM6h35mdFChhQPSi9cAI8J7DMn5kQDKX8NuBaQXAyo360Oa7tOEA==", - "dev": true - }, - "serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", - "dev": true, - "requires": { - "type-fest": "^0.13.1" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "tar-stream": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", - "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", - "dev": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true - }, - "uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", + "ansi-regex": "^4.1.0" + } + } + } + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, + "ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansi-to-html": { + "version": "0.6.14", + "resolved": "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.6.14.tgz", + "integrity": "sha512-7ZslfB1+EnFSDO5Ju+ue5Y6It19DRnZXWv8jrGHgIlPna5Mh4jz7BV5jCbQneXNFurQcKoolaaAjHtgSBfOIuA==", + "dev": true, + "requires": { + "entities": "^1.1.2" + }, + "dependencies": { + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + } + } + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" + }, + "any-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", + "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", + "dev": true + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "app-root-dir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz", + "integrity": "sha1-OBh+wt6nV3//Az/8sSFyaS/24Rg=", + "dev": true + }, + "app-root-path": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.2.1.tgz", + "integrity": "sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==" + }, + "appium": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/appium/-/appium-1.18.3.tgz", + "integrity": "sha512-oanDJJoqAqjc8pJUQholadgmIZFFqW6z1sACfPz79/SSwTfgSvQVqBB/32gNo09xgN9eQlw6ltjc9LWnHy7q4g==", + "dev": true, + "requires": { + "@babel/runtime": "^7.6.0", + "appium-android-driver": "^4.20.0", + "appium-base-driver": "^6.0.1", + "appium-espresso-driver": "^1.0.0", + "appium-fake-driver": "^1.x", + "appium-flutter-driver": "^0", + "appium-ios-driver": "4.x", + "appium-mac-driver": "1.x", + "appium-support": "2.x", + "appium-tizen-driver": "^1.1.1-beta.4", + "appium-uiautomator2-driver": "^1.37.1", + "appium-windows-driver": "1.x", + "appium-xcuitest-driver": "^3.29.0", + "appium-youiengine-driver": "^1.2.0", + "argparse": "^1.0.10", + "async-lock": "^1.0.0", + "asyncbox": "2.x", + "axios": "^0.19.2", + "bluebird": "3.x", + "continuation-local-storage": "3.x", + "dateformat": "^3.0.3", + "find-root": "^1.1.0", + "lodash": "^4.17.11", + "longjohn": "^0.2.12", + "npmlog": "4.x", + "semver": "^7.0.0", + "source-map-support": "0.x", + "teen_process": "1.x", + "winston": "3.x", + "word-wrap": "^1.2.3" + }, + "dependencies": { + "101": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/101/-/101-1.6.3.tgz", + "integrity": "sha512-4dmQ45yY0Dx24Qxp+zAsNLlMF6tteCyfVzgbulvSyC7tCyd3V8sW76sS0tHq8NpcbXfWTKasfyfzU1Kd86oKzw==", + "dev": true, + "requires": { + "clone": "^1.0.2", + "deep-eql": "^0.1.3", + "keypather": "^1.10.2" + } + }, + "@babel/polyfill": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.10.4.tgz", + "integrity": "sha512-8BYcnVqQ5kMD2HXoHInBH7H1b/uP3KdnwCYXOqFnXqguOyuu443WXusbIUbWEfY3Z0Txk0M1uG/8YuAMhNl6zg==", + "dev": true, + "requires": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", "dev": true - }, - "webdriver": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-6.8.0.tgz", - "integrity": "sha512-KijngSC7ZjgdDoFuJO70StImWB3qiiLbvURiTNQ/oeQaP0CnerQX5qvYpgMnP1CZNhxyyUbb+1+w0zBqSNfWaA==", - "dev": true, - "requires": { - "@types/lodash.merge": "^4.6.6", - "@wdio/config": "6.8.0", - "@wdio/logger": "6.8.0", - "@wdio/protocols": "6.8.0", - "@wdio/utils": "6.8.0", - "got": "^11.0.2", - "lodash.merge": "^4.6.1" - } - }, - "webdriverio": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-6.8.0.tgz", - "integrity": "sha512-jVN73xLUcIF3PJYgeqzTbyak0QfqUegq/hjaIkLl8wQWRNubuEA3dN9L4U33AzOokYvcBXinemh3Ein35f1a8A==", - "dev": true, - "requires": { - "@types/archiver": "^3.1.1", - "@types/atob": "^2.1.2", - "@types/fs-extra": "^9.0.2", - "@types/lodash.clonedeep": "^4.5.6", - "@types/lodash.isplainobject": "^4.0.6", - "@types/puppeteer-core": "^2.0.0", - "@wdio/config": "6.8.0", - "@wdio/logger": "6.8.0", - "@wdio/repl": "6.8.0", - "@wdio/utils": "6.8.0", - "archiver": "^5.0.0", - "atob": "^2.1.2", - "css-value": "^0.0.1", - "devtools": "6.8.0", - "fs-extra": "^9.0.1", - "get-port": "^5.1.1", - "grapheme-splitter": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", - "lodash.zip": "^4.2.0", - "minimatch": "^3.0.4", - "puppeteer-core": "^5.1.0", - "resq": "^1.9.1", - "rgb2hex": "^0.2.0", - "serialize-error": "^7.0.0", - "webdriver": "6.8.0" - } - }, - "zip-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.0.2.tgz", - "integrity": "sha512-TGxB2g+1ur6MHkvM644DuZr8Uzyz0k0OYWtS3YlpfWBEmK4woaC2t3+pozEL3dBfIPmpgmClR5B2QRcMgGt22g==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.0.0", - "readable-stream": "^3.6.0" - } } } }, - "appium-idb": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/appium-idb/-/appium-idb-0.4.1.tgz", - "integrity": "sha512-gMuZC/ndo4lGL2t/9sL+yyCGQVUV0rdpxgjvnuEdADA3GYCUFV/rUj2czJUhajUOhADGUP1Pibn0h3qtZuaBbQ==", + "@babel/runtime": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.4.tgz", + "integrity": "sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==", "dev": true, "requires": { - "@babel/runtime": "^7.0.0", - "appium-support": "^2.41.0", - "asyncbox": "^2.5.2", - "bluebird": "^3.1.1", - "lodash": "^4.0.0", - "shell-quote": "^1.6.1", - "source-map-support": "^0.5.5", - "teen_process": "^1.11.0" + "regenerator-runtime": "^0.13.4" + } + }, + "@dabh/diagnostics": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", + "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", + "dev": true, + "requires": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "@jimp/bmp": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.10.3.tgz", + "integrity": "sha512-keMOc5woiDmONXsB/6aXLR4Z5Q+v8lFq3EY2rcj2FmstbDMhRuGbmcBxlEgOqfRjwvtf/wOtJ3Of37oAWtVfLg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "bmp-js": "^0.1.0", + "core-js": "^3.4.1" + } + }, + "@jimp/core": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.10.3.tgz", + "integrity": "sha512-Gd5IpL3U2bFIO57Fh/OA3HCpWm4uW/pU01E75rI03BXfTdz3T+J7TwvyG1XaqsQ7/DSlS99GXtLQPlfFIe28UA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "any-base": "^1.1.0", + "buffer": "^5.2.0", + "core-js": "^3.4.1", + "exif-parser": "^0.1.12", + "file-type": "^9.0.0", + "load-bmfont": "^1.3.1", + "mkdirp": "^0.5.1", + "phin": "^2.9.1", + "pixelmatch": "^4.0.2", + "tinycolor2": "^1.4.1" }, "dependencies": { - "@jimp/bmp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.14.0.tgz", - "integrity": "sha512-5RkX6tSS7K3K3xNEb2ygPuvyL9whjanhoaB/WmmXlJS6ub4DjTqrapu8j4qnIWmO4YYtFeTbDTXV6v9P1yMA5A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "bmp-js": "^0.1.0" - } - }, - "@jimp/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.14.0.tgz", - "integrity": "sha512-S62FcKdtLtj3yWsGfJRdFXSutjvHg7aQNiFogMbwq19RP4XJWqS2nOphu7ScB8KrSlyy5nPF2hkWNhLRLyD82w==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "any-base": "^1.1.0", - "buffer": "^5.2.0", - "exif-parser": "^0.1.12", - "file-type": "^9.0.0", - "load-bmfont": "^1.3.1", - "mkdirp": "^0.5.1", - "phin": "^2.9.1", - "pixelmatch": "^4.0.2", - "tinycolor2": "^1.4.1" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "@jimp/custom": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.14.0.tgz", - "integrity": "sha512-kQJMeH87+kWJdVw8F9GQhtsageqqxrvzg7yyOw3Tx/s7v5RToe8RnKyMM+kVtBJtNAG+Xyv/z01uYQ2jiZ3GwA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/core": "^0.14.0" - } - }, - "@jimp/gif": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.14.0.tgz", - "integrity": "sha512-DHjoOSfCaCz72+oGGEh8qH0zE6pUBaBxPxxmpYJjkNyDZP7RkbBkZJScIYeQ7BmJxmGN4/dZn+MxamoQlr+UYg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "gifwrap": "^0.9.2", - "omggif": "^1.0.9" - } - }, - "@jimp/jpeg": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.14.0.tgz", - "integrity": "sha512-561neGbr+87S/YVQYnZSTyjWTHBm9F6F1obYHiyU3wVmF+1CLbxY3FQzt4YolwyQHIBv36Bo0PY2KkkU8BEeeQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "jpeg-js": "^0.4.0" - } - }, - "@jimp/plugin-blit": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.14.0.tgz", - "integrity": "sha512-YoYOrnVHeX3InfgbJawAU601iTZMwEBZkyqcP1V/S33Qnz9uzH1Uj1NtC6fNgWzvX6I4XbCWwtr4RrGFb5CFrw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-blur": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.14.0.tgz", - "integrity": "sha512-9WhZcofLrT0hgI7t0chf7iBQZib//0gJh9WcQMUt5+Q1Bk04dWs8vTgLNj61GBqZXgHSPzE4OpCrrLDBG8zlhQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-circle": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.14.0.tgz", - "integrity": "sha512-o5L+wf6QA44tvTum5HeLyLSc5eVfIUd5ZDVi5iRfO4o6GT/zux9AxuTSkKwnjhsG8bn1dDmywAOQGAx7BjrQVA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-color": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.14.0.tgz", - "integrity": "sha512-JJz512SAILYV0M5LzBb9sbOm/XEj2fGElMiHAxb7aLI6jx+n0agxtHpfpV/AePTLm1vzzDxx6AJxXbKv355hBQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "tinycolor2": "^1.4.1" - } - }, - "@jimp/plugin-contain": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.14.0.tgz", - "integrity": "sha512-RX2q233lGyaxiMY6kAgnm9ScmEkNSof0hdlaJAVDS1OgXphGAYAeSIAwzESZN4x3ORaWvkFefeVH9O9/698Evg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-cover": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.14.0.tgz", - "integrity": "sha512-0P/5XhzWES4uMdvbi3beUgfvhn4YuQ/ny8ijs5kkYIw6K8mHcl820HahuGpwWMx56DJLHRl1hFhJwo9CeTRJtQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-crop": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.14.0.tgz", - "integrity": "sha512-Ojtih+XIe6/XSGtpWtbAXBozhCdsDMmy+THUJAGu2x7ZgKrMS0JotN+vN2YC3nwDpYkM+yOJImQeptSfZb2Sug==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-displace": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.14.0.tgz", - "integrity": "sha512-c75uQUzMgrHa8vegkgUvgRL/PRvD7paFbFJvzW0Ugs8Wl+CDMGIPYQ3j7IVaQkIS+cAxv+NJ3TIRBQyBrfVEOg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-dither": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.14.0.tgz", - "integrity": "sha512-g8SJqFLyYexXQQsoh4dc1VP87TwyOgeTElBcxSXX2LaaMZezypmxQfLTzOFzZoK8m39NuaoH21Ou1Ftsq7LzVQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-fisheye": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.14.0.tgz", - "integrity": "sha512-BFfUZ64EikCaABhCA6mR3bsltWhPpS321jpeIQfJyrILdpFsZ/OccNwCgpW1XlbldDHIoNtXTDGn3E+vCE7vDg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-flip": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.14.0.tgz", - "integrity": "sha512-WtL1hj6ryqHhApih+9qZQYA6Ye8a4HAmdTzLbYdTMrrrSUgIzFdiZsD0WeDHpgS/+QMsWwF+NFmTZmxNWqKfXw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-gaussian": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.14.0.tgz", - "integrity": "sha512-uaLwQ0XAQoydDlF9tlfc7iD9drYPriFe+jgYnWm8fbw5cN+eOIcnneEX9XCOOzwgLPkNCxGox6Kxjn8zY6GxtQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-invert": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.14.0.tgz", - "integrity": "sha512-UaQW9X9vx8orQXYSjT5VcITkJPwDaHwrBbxxPoDG+F/Zgv4oV9fP+udDD6qmkgI9taU+44Fy+zm/J/gGcMWrdg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-mask": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.14.0.tgz", - "integrity": "sha512-tdiGM69OBaKtSPfYSQeflzFhEpoRZ+BvKfDEoivyTjauynbjpRiwB1CaiS8En1INTDwzLXTT0Be9SpI3LkJoEA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-normalize": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.14.0.tgz", - "integrity": "sha512-AfY8sqlsbbdVwFGcyIPy5JH/7fnBzlmuweb+Qtx2vn29okq6+HelLjw2b+VT2btgGUmWWHGEHd86oRGSoWGyEQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-print": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.14.0.tgz", - "integrity": "sha512-MwP3sH+VS5AhhSTXk7pui+tEJFsxnTKFY3TraFJb8WFbA2Vo2qsRCZseEGwpTLhENB7p/JSsLvWoSSbpmxhFAQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "load-bmfont": "^1.4.0" - } - }, - "@jimp/plugin-resize": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.14.0.tgz", - "integrity": "sha512-qFeMOyXE/Bk6QXN0GQo89+CB2dQcXqoxUcDb2Ah8wdYlKqpi53skABkgVy5pW3EpiprDnzNDboMltdvDslNgLQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-rotate": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.14.0.tgz", - "integrity": "sha512-aGaicts44bvpTcq5Dtf93/8TZFu5pMo/61lWWnYmwJJU1RqtQlxbCLEQpMyRhKDNSfPbuP8nyGmaqXlM/82J0Q==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-scale": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.14.0.tgz", - "integrity": "sha512-ZcJk0hxY5ZKZDDwflqQNHEGRblgaR+piePZm7dPwPUOSeYEH31P0AwZ1ziceR74zd8N80M0TMft+e3Td6KGBHw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-shadow": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.14.0.tgz", - "integrity": "sha512-p2igcEr/iGrLiTu0YePNHyby0WYAXM14c5cECZIVnq/UTOOIQ7xIcWZJ1lRbAEPxVVXPN1UibhZAbr3HAb5BjQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-threshold": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.14.0.tgz", - "integrity": "sha512-N4BlDgm/FoOMV/DQM2rSpzsgqAzkP0DXkWZoqaQrlRxQBo4zizQLzhEL00T/YCCMKnddzgEhnByaocgaaa0fKw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugins": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.14.0.tgz", - "integrity": "sha512-vDO3XT/YQlFlFLq5TqNjQkISqjBHT8VMhpWhAfJVwuXIpilxz5Glu4IDLK6jp4IjPR6Yg2WO8TmRY/HI8vLrOw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/plugin-blit": "^0.14.0", - "@jimp/plugin-blur": "^0.14.0", - "@jimp/plugin-circle": "^0.14.0", - "@jimp/plugin-color": "^0.14.0", - "@jimp/plugin-contain": "^0.14.0", - "@jimp/plugin-cover": "^0.14.0", - "@jimp/plugin-crop": "^0.14.0", - "@jimp/plugin-displace": "^0.14.0", - "@jimp/plugin-dither": "^0.14.0", - "@jimp/plugin-fisheye": "^0.14.0", - "@jimp/plugin-flip": "^0.14.0", - "@jimp/plugin-gaussian": "^0.14.0", - "@jimp/plugin-invert": "^0.14.0", - "@jimp/plugin-mask": "^0.14.0", - "@jimp/plugin-normalize": "^0.14.0", - "@jimp/plugin-print": "^0.14.0", - "@jimp/plugin-resize": "^0.14.0", - "@jimp/plugin-rotate": "^0.14.0", - "@jimp/plugin-scale": "^0.14.0", - "@jimp/plugin-shadow": "^0.14.0", - "@jimp/plugin-threshold": "^0.14.0", - "timm": "^1.6.1" - } - }, - "@jimp/png": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.14.0.tgz", - "integrity": "sha512-0RV/mEIDOrPCcNfXSPmPBqqSZYwGADNRVUTyMt47RuZh7sugbYdv/uvKmQSiqRdR0L1sfbCBMWUEa5G/8MSbdA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "pngjs": "^3.3.3" - } - }, - "@jimp/tiff": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.14.0.tgz", - "integrity": "sha512-zBYDTlutc7j88G/7FBCn3kmQwWr0rmm1e0FKB4C3uJ5oYfT8645lftUsvosKVUEfkdmOaMAnhrf4ekaHcb5gQw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "utif": "^2.0.1" - } - }, - "@jimp/types": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.14.0.tgz", - "integrity": "sha512-hx3cXAW1KZm+b+XCrY3LXtdWy2U+hNtq0rPyJ7NuXCjU7lZR3vIkpz1DLJ3yDdS70hTi5QDXY3Cd9kd6DtloHQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/bmp": "^0.14.0", - "@jimp/gif": "^0.14.0", - "@jimp/jpeg": "^0.14.0", - "@jimp/png": "^0.14.0", - "@jimp/tiff": "^0.14.0", - "timm": "^1.6.1" - } - }, - "@jimp/utils": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.14.0.tgz", - "integrity": "sha512-MY5KFYUru0y74IsgM/9asDwb3ERxWxXEu3CRCZEvE7DtT86y1bR1XgtlSliMrptjz4qbivNGMQSvUBpEFJDp1A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "regenerator-runtime": "^0.13.3" - } - }, - "appium-support": { - "version": "2.49.0", - "resolved": "https://registry.npmjs.org/appium-support/-/appium-support-2.49.0.tgz", - "integrity": "sha512-PtUsfpCjdGqZTTuRyH5U9iOGq9SpEVHg0H6/2HrazkX6ZvSQ+kIVYHTAGIXVeBJecfN9/syqgMmEekVgD15BGA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "archiver": "^5.0.0", - "axios": "^0.20.0", - "base64-stream": "^1.0.0", - "bluebird": "^3.5.1", - "bplist-creator": "^0", - "bplist-parser": "^0.2", - "form-data": "^3.0.0", - "get-stream": "^6.0.0", - "glob": "^7.1.2", - "jimp": "^0.14.0", - "jsftp": "^2.1.2", - "klaw": "^3.0.0", - "lockfile": "^1.0.4", - "lodash": "^4.2.1", - "mjpeg-server": "^0.3.0", - "mkdirp": "^1.0.0", - "moment": "^2.24.0", - "mv": "^2.1.1", - "ncp": "^2.0.0", - "npmlog": "^4.1.2", - "plist": "^3.0.1", - "pluralize": "^8.0.0", - "pngjs": "^5.0.0", - "rimraf": "^3.0.0", - "sanitize-filename": "^1.6.1", - "semver": "^7.0.0", - "shell-quote": "^1.7.2", - "source-map-support": "^0.5.5", - "teen_process": "^1.5.1", - "uuid": "^8.0.0", - "which": "^2.0.0", - "yauzl": "^2.7.0" - }, - "dependencies": { - "jimp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.14.0.tgz", - "integrity": "sha512-8BXU+J8+SPmwwyq9ELihpSV4dWPTiOKBWCEgtkbnxxAVMjXdf3yGmyaLSshBfXc8sP/JQ9OZj5R8nZzz2wPXgA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/custom": "^0.14.0", - "@jimp/plugins": "^0.14.0", - "@jimp/types": "^0.14.0", - "regenerator-runtime": "^0.13.3" - } - }, - "pngjs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", - "dev": true - } - } - }, - "archiver": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.0.2.tgz", - "integrity": "sha512-Tq3yV/T4wxBsD2Wign8W9VQKhaUxzzRmjEiSoOK0SLqPgDP/N1TKdYyBeIEu56T4I9iO4fKTTR0mN9NWkBA0sg==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "async": "^3.2.0", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", - "tar-stream": "^2.1.4", - "zip-stream": "^4.0.0" - }, - "dependencies": { - "async": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", - "dev": true - } - } - }, - "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dev": true, - "requires": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } - } - }, - "axios": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", - "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", - "dev": true, - "requires": { - "follow-redirects": "^1.10.0" - } - }, - "bl": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "compress-commons": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.0.1.tgz", - "integrity": "sha512-xZm9o6iikekkI0GnXCmAl3LQGZj5TBDj0zLowsqi7tJtEa3FMGSEcHcqrSJIrOAk1UG/NBbDn/F1q+MG/p/EsA==", - "dev": true, - "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.0", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - } - }, - "crc32-stream": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.0.tgz", - "integrity": "sha512-tyMw2IeUX6t9jhgXI6um0eKfWq4EIDpfv5m7GX4Jzp7eVelQ360xd8EPXJhp2mHwLQIkqlnMLjzqSZI3a+0wRw==", - "dev": true, - "requires": { - "crc": "^3.4.4", - "readable-stream": "^3.4.0" - } - }, - "follow-redirects": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", - "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", - "dev": true - }, - "form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", - "dev": true - }, - "jpeg-js": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.2.tgz", - "integrity": "sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw==", - "dev": true - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "tar-stream": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", - "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", - "dev": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", - "dev": true - }, - "zip-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.0.2.tgz", - "integrity": "sha512-TGxB2g+1ur6MHkvM644DuZr8Uzyz0k0OYWtS3YlpfWBEmK4woaC2t3+pozEL3dBfIPmpgmClR5B2QRcMgGt22g==", + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "dev": true, "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.0.0", - "readable-stream": "^3.6.0" + "minimist": "^1.2.5" } } } }, - "appium-ios-device": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/appium-ios-device/-/appium-ios-device-1.5.0.tgz", - "integrity": "sha512-s05omp42+ObeyTI6SQ2HcommWiX3Y5Bfpvn1D1ftyl01u/qr9QLyDzBn47myYB80MeDcwaR/iRADDLlXkpR4Dg==", + "@jimp/custom": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.10.3.tgz", + "integrity": "sha512-nZmSI+jwTi5IRyNLbKSXQovoeqsw+D0Jn0SxW08wYQvdkiWA8bTlDQFgQ7HVwCAKBm8oKkDB/ZEo9qvHJ+1gAQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/core": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/gif": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.10.3.tgz", + "integrity": "sha512-vjlRodSfz1CrUvvrnUuD/DsLK1GHB/yDZXHthVdZu23zYJIW7/WrIiD1IgQ5wOMV7NocfrvPn2iqUfBP81/WWA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1", + "omggif": "^1.0.9" + } + }, + "@jimp/jpeg": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.10.3.tgz", + "integrity": "sha512-AAANwgUZOt6f6P7LZxY9lyJ9xclqutYJlsxt3JbriXUGJgrrFAIkcKcqv1nObgmQASSAQKYaMV9KdHjMlWFKlQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1", + "jpeg-js": "^0.3.4" + } + }, + "@jimp/plugin-blit": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.10.3.tgz", + "integrity": "sha512-5zlKlCfx4JWw9qUVC7GI4DzXyxDWyFvgZLaoGFoT00mlXlN75SarlDwc9iZ/2e2kp4bJWxz3cGgG4G/WXrbg3Q==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugin-blur": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.10.3.tgz", + "integrity": "sha512-cTOK3rjh1Yjh23jSfA6EHCHjsPJDEGLC8K2y9gM7dnTUK1y9NNmkFS23uHpyjgsWFIoH9oRh2SpEs3INjCpZhQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugin-circle": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.10.3.tgz", + "integrity": "sha512-51GAPIVelqAcfuUpaM5JWJ0iWl4vEjNXB7p4P7SX5udugK5bxXUjO6KA2qgWmdpHuCKtoNgkzWU9fNSuYp7tCA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugin-color": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.10.3.tgz", + "integrity": "sha512-RgeHUElmlTH7vpI4WyQrz6u59spiKfVQbsG/XUzfWGamFSixa24ZDwX/yV/Ts+eNaz7pZeIuv533qmKPvw2ujg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1", + "tinycolor2": "^1.4.1" + } + }, + "@jimp/plugin-contain": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.10.3.tgz", + "integrity": "sha512-bYJKW9dqzcB0Ihc6u7jSyKa3juStzbLs2LFr6fu8TzA2WkMS/R8h+ddkiO36+F9ILTWHP0CIA3HFe5OdOGcigw==", "dev": true, "requires": { - "@babel/runtime": "^7.0.0", - "appium-support": "^2.35.0", - "bluebird": "^3.1.1", - "lodash": "^4.17.15", - "semver": "^7.0.0", - "source-map-support": "^0.5.5" + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" } }, - "appium-ios-driver": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/appium-ios-driver/-/appium-ios-driver-4.6.0.tgz", - "integrity": "sha512-pEE8ZJYcmB3CLs+EqlAsFOJCsJ6v6K1m//UXIU8r29w2PHs9d4LmFXvOESUBK7CIrlJmwCcr6eAZhtsr0H1fuw==", + "@jimp/plugin-cover": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.10.3.tgz", + "integrity": "sha512-pOxu0cM0BRPzdV468n4dMocJXoMbTnARDY/EpC3ZW15SpMuc/dr1KhWQHgoQX5kVW1Wt8zgqREAJJCQ5KuPKDA==", "dev": true, "requires": { - "@babel/runtime": "^7.0.0", - "appium-base-driver": "^5.0.0", - "appium-ios-simulator": "^3.9.0", - "appium-remote-debugger": "^4.1.0", - "appium-support": "^2.25.0", - "appium-xcode": "^3.1.0", - "asyncbox": "^2.3.1", - "bluebird": "^3.5.1", - "colors": "^1.1.2", - "js2xmlparser2": "^0.2.0", - "lodash": "^4.13.1", - "moment": "^2.24.0", - "moment-timezone": "^0.5.26", - "node-idevice": "^0.1.6", - "node-simctl": "^5.0.0", - "pem": "^1.8.3", - "portfinder": "^1.0.13", - "request": "^2.79.0", - "request-promise": "^4.1.1", - "safari-launcher": "^2.0.5", - "source-map-support": "^0.5.5", - "teen_process": "^1.6.0", - "through": "^2.3.8", - "uuid-js": "^0.7.5", - "xmldom": "^0.2.0", - "xpath": "^0.0.24", - "yargs": "^15.0.1" + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugin-crop": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.10.3.tgz", + "integrity": "sha512-nB7HgOjjl9PgdHr076xZ3Sr6qHYzeBYBs9qvs3tfEEUeYMNnvzgCCGtUl6eMakazZFCMk3mhKmcB9zQuHFOvkg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugin-displace": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.10.3.tgz", + "integrity": "sha512-8t3fVKCH5IVqI4lewe4lFFjpxxr69SQCz5/tlpDLQZsrNScNJivHdQ09zljTrVTCSgeCqQJIKgH2Q7Sk/pAZ0w==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugin-dither": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.10.3.tgz", + "integrity": "sha512-JCX/oNSnEg1kGQ8ffZ66bEgQOLCY3Rn+lrd6v1jjLy/mn9YVZTMsxLtGCXpiCDC2wG/KTmi4862ysmP9do9dAQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugin-fisheye": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.10.3.tgz", + "integrity": "sha512-RRZb1wqe+xdocGcFtj2xHU7sF7xmEZmIa6BmrfSchjyA2b32TGPWKnP3qyj7p6LWEsXn+19hRYbjfyzyebPElQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugin-flip": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.10.3.tgz", + "integrity": "sha512-0epbi8XEzp0wmSjoW9IB0iMu0yNF17aZOxLdURCN3Zr+8nWPs5VNIMqSVa1Y62GSyiMDpVpKF/ITiXre+EqrPg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugin-gaussian": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.10.3.tgz", + "integrity": "sha512-25eHlFbHUDnMMGpgRBBeQ2AMI4wsqCg46sue0KklI+c2BaZ+dGXmJA5uT8RTOrt64/K9Wz5E+2n7eBnny4dfpQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugin-invert": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.10.3.tgz", + "integrity": "sha512-effYSApWY/FbtlzqsKXlTLkgloKUiHBKjkQnqh5RL4oQxh/33j6aX+HFdDyQKtsXb8CMd4xd7wyiD2YYabTa0g==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugin-mask": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.10.3.tgz", + "integrity": "sha512-twrg8q8TIhM9Z6Jcu9/5f+OCAPaECb0eKrrbbIajJqJ3bCUlj5zbfgIhiQIzjPJ6KjpnFPSqHQfHkU1Vvk/nVw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugin-normalize": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.10.3.tgz", + "integrity": "sha512-xkb5eZI/mMlbwKkDN79+1/t/+DBo8bBXZUMsT4gkFgMRKNRZ6NQPxlv1d3QpRzlocsl6UMxrHnhgnXdLAcgrXw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugin-print": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.10.3.tgz", + "integrity": "sha512-wjRiI6yjXsAgMe6kVjizP+RgleUCLkH256dskjoNvJzmzbEfO7xQw9g6M02VET+emnbY0CO83IkrGm2q43VRyg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1", + "load-bmfont": "^1.4.0" + } + }, + "@jimp/plugin-resize": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.10.3.tgz", + "integrity": "sha512-rf8YmEB1d7Sg+g4LpqF0Mp+dfXfb6JFJkwlAIWPUOR7lGsPWALavEwTW91c0etEdnp0+JB9AFpy6zqq7Lwkq6w==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugin-rotate": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.10.3.tgz", + "integrity": "sha512-YXLlRjm18fkW9MOHUaVAxWjvgZM851ofOipytz5FyKp4KZWDLk+dZK1JNmVmK7MyVmAzZ5jsgSLhIgj+GgN0Eg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugin-scale": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.10.3.tgz", + "integrity": "sha512-5DXD7x7WVcX1gUgnlFXQa8F+Q3ThRYwJm+aesgrYvDOY+xzRoRSdQvhmdd4JEEue3lyX44DvBSgCIHPtGcEPaw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugin-shadow": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.10.3.tgz", + "integrity": "sha512-/nkFXpt2zVcdP4ETdkAUL0fSzyrC5ZFxdcphbYBodqD7fXNqChS/Un1eD4xCXWEpW8cnG9dixZgQgStjywH0Mg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugin-threshold": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.10.3.tgz", + "integrity": "sha512-Dzh0Yq2wXP2SOnxcbbiyA4LJ2luwrdf1MghNIt9H+NX7B+IWw/N8qA2GuSm9n4BPGSLluuhdAWJqHcTiREriVA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1" + } + }, + "@jimp/plugins": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.10.3.tgz", + "integrity": "sha512-jTT3/7hOScf0EIKiAXmxwayHhryhc1wWuIe3FrchjDjr9wgIGNN2a7XwCgPl3fML17DXK1x8EzDneCdh261bkw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/plugin-blit": "^0.10.3", + "@jimp/plugin-blur": "^0.10.3", + "@jimp/plugin-circle": "^0.10.3", + "@jimp/plugin-color": "^0.10.3", + "@jimp/plugin-contain": "^0.10.3", + "@jimp/plugin-cover": "^0.10.3", + "@jimp/plugin-crop": "^0.10.3", + "@jimp/plugin-displace": "^0.10.3", + "@jimp/plugin-dither": "^0.10.3", + "@jimp/plugin-fisheye": "^0.10.3", + "@jimp/plugin-flip": "^0.10.3", + "@jimp/plugin-gaussian": "^0.10.3", + "@jimp/plugin-invert": "^0.10.3", + "@jimp/plugin-mask": "^0.10.3", + "@jimp/plugin-normalize": "^0.10.3", + "@jimp/plugin-print": "^0.10.3", + "@jimp/plugin-resize": "^0.10.3", + "@jimp/plugin-rotate": "^0.10.3", + "@jimp/plugin-scale": "^0.10.3", + "@jimp/plugin-shadow": "^0.10.3", + "@jimp/plugin-threshold": "^0.10.3", + "core-js": "^3.4.1", + "timm": "^1.6.1" + } + }, + "@jimp/png": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.10.3.tgz", + "integrity": "sha512-YKqk/dkl+nGZxSYIDQrqhmaP8tC3IK8H7dFPnnzFVvbhDnyYunqBZZO3SaZUKTichClRw8k/CjBhbc+hifSGWg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "@jimp/utils": "^0.10.3", + "core-js": "^3.4.1", + "pngjs": "^3.3.3" }, - "dependencies": { - "@wdio/config": { - "version": "5.22.4", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-5.22.4.tgz", - "integrity": "sha512-i5dJQWb80darcRA//tfG0guMeQCeRUXroZNnHjGNb1qzvTRZmcIIhdxaD+DbK/5dWEx6aoMfoi6wjVp/CXwdAg==", - "dev": true, - "requires": { - "@wdio/logger": "5.16.10", - "deepmerge": "^4.0.0", - "glob": "^7.1.2" - } - }, - "@wdio/logger": { - "version": "5.16.10", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-5.16.10.tgz", - "integrity": "sha512-hRKhxgd9uB48Dtj2xe2ckxU4KwI/RO8IwguySuaI2SLFj6EDbdonwzpVkq111/fjBuq7R1NauAaNcm3AMEbIFA==", - "dev": true, - "requires": { - "chalk": "^3.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - } - }, - "@wdio/protocols": { - "version": "5.22.1", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-5.22.1.tgz", - "integrity": "sha512-GdoWb/HTrb09Qb0S/7sLp1NU94LAhTsF1NnFj5sEFSUpecrl0S07pnhVg54pUImectN/woaqSl7uJGjlSGZcdQ==", - "dev": true - }, - "@wdio/repl": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-5.23.0.tgz", - "integrity": "sha512-cKG9m0XuqcQenQmoup0yJX1fkDQEdY06QXuwt636ZQf6XgDoeoAdNOgnRnNruQ0+JsC2eqHFoSNto1q8wcLH/g==", - "dev": true, - "requires": { - "@wdio/utils": "5.23.0" - } - }, - "@wdio/utils": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-5.23.0.tgz", - "integrity": "sha512-dWPEkDiaNUqJXPO6L2di2apI7Rle7Er4euh67Wlb5+3MrPNjCKhiF8gHcpQeL8oe6A1MH/f89kpSEEXe4BMkAw==", - "dev": true, - "requires": { - "@wdio/logger": "5.16.10", - "deepmerge": "^4.0.0" - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "appium-remote-debugger": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/appium-remote-debugger/-/appium-remote-debugger-4.5.0.tgz", - "integrity": "sha512-8UhFOQJvSCHSuWloGe+FOeIObIp6wzc5oaXV8vVRANn8rqZlQl2j4dgelQcA1DoE54Lphy00Bo99M7qDsKkF7w==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "appium-base-driver": "^4.0.0", - "appium-support": "^2.28.0", - "asyncbox": "^2.5.2", - "bluebird": "^3.4.7", - "bufferpack": "0.0.6", - "es6-error": "^4.1.1", - "lodash": "^4.17.11", - "request": "^2.79.0", - "request-promise": "^4.1.1", - "source-map-support": "^0.5.5", - "ws": "^7.0.0" - }, - "dependencies": { - "appium-base-driver": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/appium-base-driver/-/appium-base-driver-4.5.1.tgz", - "integrity": "sha512-g7sI5mzmGdZhIFg3+A5f9ewtihYKS0b33wZWbN6R/PYYTEmXbNhFPGfVqzoAzFANuLjlTlvEEsqGcUtjrMO2Bw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "appium-support": "^2.33.1", - "async-lock": "^1.0.0", - "asyncbox": "^2.3.1", - "bluebird": "^3.5.3", - "body-parser": "^1.18.2", - "colors": "^1.1.2", - "es6-error": "^4.1.1", - "express": "^4.16.2", - "http-status-codes": "^1.3.0", - "lodash": "^4.0.0", - "lru-cache": "^5.0.0", - "method-override": "^3.0.0", - "morgan": "^1.9.0", - "request": "^2.83.0", - "request-promise": "^4.2.2", - "sanitize-filename": "^1.6.1", - "serve-favicon": "^2.4.5", - "source-map-support": "^0.5.5", - "uuid-js": "^0.7.5", - "validate.js": "^0.13.0", - "webdriverio": "^5.10.9", - "ws": "^7.0.0" - } - } - } - }, - "archiver": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-3.1.1.tgz", - "integrity": "sha512-5Hxxcig7gw5Jod/8Gq0OneVgLYET+oNHcxgWItq4TbhOzRLKNAFUb9edAftiMKXvXfCB0vbGrJdZDNq0dWMsxg==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "async": "^2.6.3", - "buffer-crc32": "^0.2.1", - "glob": "^7.1.4", - "readable-stream": "^3.4.0", - "tar-stream": "^2.1.0", - "zip-stream": "^2.1.2" - } - }, - "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dev": true, - "requires": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } - } - }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "bl": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "compress-commons": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-2.1.1.tgz", - "integrity": "sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q==", - "dev": true, - "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^3.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^2.3.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } - } - }, - "crc32-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-3.0.1.tgz", - "integrity": "sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==", - "dev": true, - "requires": { - "crc": "^3.4.4", - "readable-stream": "^3.4.0" - } - }, - "node-simctl": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/node-simctl/-/node-simctl-5.3.0.tgz", - "integrity": "sha512-5EJPsorg+ZLxeLk6Cc5Q3hI4WcWkWSWkqQnJEJf3DrM9UekGvpgOUE8uZtr8dTtY/GZCsI30Ksusqm+zGq3NsA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "appium-support": "^2.37.0", - "appium-xcode": "^3.8.0", - "asyncbox": "^2.3.1", - "lodash": "^4.2.1", - "semver": "^7.0.0", - "source-map-support": "^0.5.5", - "teen_process": "^1.5.1" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "rgb2hex": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.10.tgz", - "integrity": "sha512-vKz+kzolWbL3rke/xeTE2+6vHmZnNxGyDnaVW4OckntAIcc7DcZzWkQSfxMDwqHS8vhgySnIFyBUH7lIk6PxvQ==", - "dev": true - }, - "serialize-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-5.0.0.tgz", - "integrity": "sha512-/VtpuyzYf82mHYTtI4QKtwHa79vAdU5OQpNPAmE/0UDdlGT0ZxHwC+J6gXkw29wwoVI8fMPsfcVHOwXtUQYYQA==", - "dev": true, - "requires": { - "type-fest": "^0.8.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "tar-stream": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", - "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", - "dev": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "webdriver": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-5.23.0.tgz", - "integrity": "sha512-r7IrbZ2SuTIRyWV8mv4a4hZoFcT9Qt4MznOkdRWPE1sPpZ8lyLZsIEjKCEbHelOzPwURqk+biwGrm4z2OZRAiw==", - "dev": true, - "requires": { - "@types/request": "^2.48.4", - "@wdio/config": "5.22.4", - "@wdio/logger": "5.16.10", - "@wdio/protocols": "5.22.1", - "@wdio/utils": "5.23.0", - "lodash.merge": "^4.6.1", - "request": "^2.83.0" - } - }, - "webdriverio": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-5.23.0.tgz", - "integrity": "sha512-hxt6Nuu2bBrTsVk7GfoFRTh63l4fRVXlK9U30RtPbHoWO5tcFdyUz2UTgeHEZ54ea1DQEVPfsgFiLnJULkWp1Q==", - "dev": true, - "requires": { - "@wdio/config": "5.22.4", - "@wdio/logger": "5.16.10", - "@wdio/repl": "5.23.0", - "@wdio/utils": "5.23.0", - "archiver": "^3.0.0", - "css-value": "^0.0.1", - "grapheme-splitter": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", - "lodash.zip": "^4.2.0", - "resq": "^1.6.0", - "rgb2hex": "^0.1.0", - "serialize-error": "^5.0.0", - "webdriver": "5.23.0" - } - }, - "xmldom": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.2.1.tgz", - "integrity": "sha512-kXXiYvmblIgEemGeB75y97FyaZavx6SQhGppLw5TKWAD2Wd0KAly0g23eVLh17YcpxZpnFym1Qk/eaRjy1APPg==", - "dev": true - }, - "xpath": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.24.tgz", - "integrity": "sha1-Gt4WLhzFI8jTn8fQavwW6iFvKfs=", - "dev": true - }, - "zip-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.3.tgz", - "integrity": "sha512-EkXc2JGcKhO5N5aZ7TmuNo45budRaFGHOmz24wtJR7znbNqDPmdZtUauKX6et8KAVseAMBOyWJqEpXcHTBsh7Q==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^2.1.1", - "readable-stream": "^3.4.0" - } + "dependencies": { + "pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "dev": true } } }, - "appium-ios-simulator": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/appium-ios-simulator/-/appium-ios-simulator-3.20.0.tgz", - "integrity": "sha512-RbnQrlCeRNhkftXkt97pQARfVNeCxp6v2v6RaZV1ZU5D2HrY5lEaw7I65wqUxn75w6hywiz23+y//6+OaLnfog==", + "@jimp/tiff": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.10.3.tgz", + "integrity": "sha512-7EsJzZ5Y/EtinkBGuwX3Bi4S+zgbKouxjt9c82VJTRJOQgLWsE/RHqcyRCOQBhHAZ9QexYmDz34medfLKdoX0g==", "dev": true, "requires": { - "@babel/runtime": "^7.0.0", - "appium-support": "^2.39.0", - "appium-xcode": "^3.1.0", - "async-lock": "^1.0.0", - "asyncbox": "^2.3.1", - "bluebird": "^3.5.1", - "fkill": "^7.0.0", - "lodash": "^4.2.1", - "node-simctl": "^6.1.0", - "openssl-wrapper": "^0.3.4", - "semver": "^7.0.0", - "shell-quote": "^1.6.1", - "source-map-support": "^0.5.3", - "teen_process": "^1.3.0" + "@babel/runtime": "^7.7.2", + "core-js": "^3.4.1", + "utif": "^2.0.1" } }, - "appium-mac-driver": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/appium-mac-driver/-/appium-mac-driver-1.10.0.tgz", - "integrity": "sha512-+isEfxtE7FQJr418udxfHBp9OU/S/tEjG8BbMgGjcHi8TE/4B78KzQZxgewGPRKraNLFo/58by8PqgWsS6sLtA==", + "@jimp/types": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.10.3.tgz", + "integrity": "sha512-XGmBakiHZqseSWr/puGN+CHzx0IKBSpsKlmEmsNV96HKDiP6eu8NSnwdGCEq2mmIHe0JNcg1hqg59hpwtQ7Tiw==", "dev": true, "requires": { - "@babel/runtime": "^7.0.0", - "appium-base-driver": "^5.0.0", - "appium-support": "^2.36.0", - "asyncbox": "^2.3.1", - "bluebird": "^3.5.1", - "lodash": "^4.17.4", - "source-map-support": "^0.5.5", - "teen_process": "^1.15.0", - "yargs": "^15.0.1" - }, - "dependencies": { - "@jimp/bmp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.14.0.tgz", - "integrity": "sha512-5RkX6tSS7K3K3xNEb2ygPuvyL9whjanhoaB/WmmXlJS6ub4DjTqrapu8j4qnIWmO4YYtFeTbDTXV6v9P1yMA5A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "bmp-js": "^0.1.0" - } - }, - "@jimp/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.14.0.tgz", - "integrity": "sha512-S62FcKdtLtj3yWsGfJRdFXSutjvHg7aQNiFogMbwq19RP4XJWqS2nOphu7ScB8KrSlyy5nPF2hkWNhLRLyD82w==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "any-base": "^1.1.0", - "buffer": "^5.2.0", - "exif-parser": "^0.1.12", - "file-type": "^9.0.0", - "load-bmfont": "^1.3.1", - "mkdirp": "^0.5.1", - "phin": "^2.9.1", - "pixelmatch": "^4.0.2", - "tinycolor2": "^1.4.1" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "@jimp/custom": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.14.0.tgz", - "integrity": "sha512-kQJMeH87+kWJdVw8F9GQhtsageqqxrvzg7yyOw3Tx/s7v5RToe8RnKyMM+kVtBJtNAG+Xyv/z01uYQ2jiZ3GwA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/core": "^0.14.0" - } - }, - "@jimp/gif": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.14.0.tgz", - "integrity": "sha512-DHjoOSfCaCz72+oGGEh8qH0zE6pUBaBxPxxmpYJjkNyDZP7RkbBkZJScIYeQ7BmJxmGN4/dZn+MxamoQlr+UYg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "gifwrap": "^0.9.2", - "omggif": "^1.0.9" - } - }, - "@jimp/jpeg": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.14.0.tgz", - "integrity": "sha512-561neGbr+87S/YVQYnZSTyjWTHBm9F6F1obYHiyU3wVmF+1CLbxY3FQzt4YolwyQHIBv36Bo0PY2KkkU8BEeeQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "jpeg-js": "^0.4.0" - } - }, - "@jimp/plugin-blit": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.14.0.tgz", - "integrity": "sha512-YoYOrnVHeX3InfgbJawAU601iTZMwEBZkyqcP1V/S33Qnz9uzH1Uj1NtC6fNgWzvX6I4XbCWwtr4RrGFb5CFrw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-blur": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.14.0.tgz", - "integrity": "sha512-9WhZcofLrT0hgI7t0chf7iBQZib//0gJh9WcQMUt5+Q1Bk04dWs8vTgLNj61GBqZXgHSPzE4OpCrrLDBG8zlhQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-circle": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.14.0.tgz", - "integrity": "sha512-o5L+wf6QA44tvTum5HeLyLSc5eVfIUd5ZDVi5iRfO4o6GT/zux9AxuTSkKwnjhsG8bn1dDmywAOQGAx7BjrQVA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-color": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.14.0.tgz", - "integrity": "sha512-JJz512SAILYV0M5LzBb9sbOm/XEj2fGElMiHAxb7aLI6jx+n0agxtHpfpV/AePTLm1vzzDxx6AJxXbKv355hBQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "tinycolor2": "^1.4.1" - } - }, - "@jimp/plugin-contain": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.14.0.tgz", - "integrity": "sha512-RX2q233lGyaxiMY6kAgnm9ScmEkNSof0hdlaJAVDS1OgXphGAYAeSIAwzESZN4x3ORaWvkFefeVH9O9/698Evg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-cover": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.14.0.tgz", - "integrity": "sha512-0P/5XhzWES4uMdvbi3beUgfvhn4YuQ/ny8ijs5kkYIw6K8mHcl820HahuGpwWMx56DJLHRl1hFhJwo9CeTRJtQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-crop": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.14.0.tgz", - "integrity": "sha512-Ojtih+XIe6/XSGtpWtbAXBozhCdsDMmy+THUJAGu2x7ZgKrMS0JotN+vN2YC3nwDpYkM+yOJImQeptSfZb2Sug==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-displace": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.14.0.tgz", - "integrity": "sha512-c75uQUzMgrHa8vegkgUvgRL/PRvD7paFbFJvzW0Ugs8Wl+CDMGIPYQ3j7IVaQkIS+cAxv+NJ3TIRBQyBrfVEOg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-dither": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.14.0.tgz", - "integrity": "sha512-g8SJqFLyYexXQQsoh4dc1VP87TwyOgeTElBcxSXX2LaaMZezypmxQfLTzOFzZoK8m39NuaoH21Ou1Ftsq7LzVQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-fisheye": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.14.0.tgz", - "integrity": "sha512-BFfUZ64EikCaABhCA6mR3bsltWhPpS321jpeIQfJyrILdpFsZ/OccNwCgpW1XlbldDHIoNtXTDGn3E+vCE7vDg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-flip": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.14.0.tgz", - "integrity": "sha512-WtL1hj6ryqHhApih+9qZQYA6Ye8a4HAmdTzLbYdTMrrrSUgIzFdiZsD0WeDHpgS/+QMsWwF+NFmTZmxNWqKfXw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-gaussian": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.14.0.tgz", - "integrity": "sha512-uaLwQ0XAQoydDlF9tlfc7iD9drYPriFe+jgYnWm8fbw5cN+eOIcnneEX9XCOOzwgLPkNCxGox6Kxjn8zY6GxtQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-invert": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.14.0.tgz", - "integrity": "sha512-UaQW9X9vx8orQXYSjT5VcITkJPwDaHwrBbxxPoDG+F/Zgv4oV9fP+udDD6qmkgI9taU+44Fy+zm/J/gGcMWrdg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-mask": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.14.0.tgz", - "integrity": "sha512-tdiGM69OBaKtSPfYSQeflzFhEpoRZ+BvKfDEoivyTjauynbjpRiwB1CaiS8En1INTDwzLXTT0Be9SpI3LkJoEA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-normalize": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.14.0.tgz", - "integrity": "sha512-AfY8sqlsbbdVwFGcyIPy5JH/7fnBzlmuweb+Qtx2vn29okq6+HelLjw2b+VT2btgGUmWWHGEHd86oRGSoWGyEQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-print": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.14.0.tgz", - "integrity": "sha512-MwP3sH+VS5AhhSTXk7pui+tEJFsxnTKFY3TraFJb8WFbA2Vo2qsRCZseEGwpTLhENB7p/JSsLvWoSSbpmxhFAQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "load-bmfont": "^1.4.0" - } - }, - "@jimp/plugin-resize": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.14.0.tgz", - "integrity": "sha512-qFeMOyXE/Bk6QXN0GQo89+CB2dQcXqoxUcDb2Ah8wdYlKqpi53skABkgVy5pW3EpiprDnzNDboMltdvDslNgLQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-rotate": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.14.0.tgz", - "integrity": "sha512-aGaicts44bvpTcq5Dtf93/8TZFu5pMo/61lWWnYmwJJU1RqtQlxbCLEQpMyRhKDNSfPbuP8nyGmaqXlM/82J0Q==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-scale": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.14.0.tgz", - "integrity": "sha512-ZcJk0hxY5ZKZDDwflqQNHEGRblgaR+piePZm7dPwPUOSeYEH31P0AwZ1ziceR74zd8N80M0TMft+e3Td6KGBHw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-shadow": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.14.0.tgz", - "integrity": "sha512-p2igcEr/iGrLiTu0YePNHyby0WYAXM14c5cECZIVnq/UTOOIQ7xIcWZJ1lRbAEPxVVXPN1UibhZAbr3HAb5BjQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-threshold": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.14.0.tgz", - "integrity": "sha512-N4BlDgm/FoOMV/DQM2rSpzsgqAzkP0DXkWZoqaQrlRxQBo4zizQLzhEL00T/YCCMKnddzgEhnByaocgaaa0fKw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugins": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.14.0.tgz", - "integrity": "sha512-vDO3XT/YQlFlFLq5TqNjQkISqjBHT8VMhpWhAfJVwuXIpilxz5Glu4IDLK6jp4IjPR6Yg2WO8TmRY/HI8vLrOw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/plugin-blit": "^0.14.0", - "@jimp/plugin-blur": "^0.14.0", - "@jimp/plugin-circle": "^0.14.0", - "@jimp/plugin-color": "^0.14.0", - "@jimp/plugin-contain": "^0.14.0", - "@jimp/plugin-cover": "^0.14.0", - "@jimp/plugin-crop": "^0.14.0", - "@jimp/plugin-displace": "^0.14.0", - "@jimp/plugin-dither": "^0.14.0", - "@jimp/plugin-fisheye": "^0.14.0", - "@jimp/plugin-flip": "^0.14.0", - "@jimp/plugin-gaussian": "^0.14.0", - "@jimp/plugin-invert": "^0.14.0", - "@jimp/plugin-mask": "^0.14.0", - "@jimp/plugin-normalize": "^0.14.0", - "@jimp/plugin-print": "^0.14.0", - "@jimp/plugin-resize": "^0.14.0", - "@jimp/plugin-rotate": "^0.14.0", - "@jimp/plugin-scale": "^0.14.0", - "@jimp/plugin-shadow": "^0.14.0", - "@jimp/plugin-threshold": "^0.14.0", - "timm": "^1.6.1" - } - }, - "@jimp/png": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.14.0.tgz", - "integrity": "sha512-0RV/mEIDOrPCcNfXSPmPBqqSZYwGADNRVUTyMt47RuZh7sugbYdv/uvKmQSiqRdR0L1sfbCBMWUEa5G/8MSbdA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "pngjs": "^3.3.3" - }, - "dependencies": { - "pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", - "dev": true - } - } - }, - "@jimp/tiff": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.14.0.tgz", - "integrity": "sha512-zBYDTlutc7j88G/7FBCn3kmQwWr0rmm1e0FKB4C3uJ5oYfT8645lftUsvosKVUEfkdmOaMAnhrf4ekaHcb5gQw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "utif": "^2.0.1" - } - }, - "@jimp/types": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.14.0.tgz", - "integrity": "sha512-hx3cXAW1KZm+b+XCrY3LXtdWy2U+hNtq0rPyJ7NuXCjU7lZR3vIkpz1DLJ3yDdS70hTi5QDXY3Cd9kd6DtloHQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/bmp": "^0.14.0", - "@jimp/gif": "^0.14.0", - "@jimp/jpeg": "^0.14.0", - "@jimp/png": "^0.14.0", - "@jimp/tiff": "^0.14.0", - "timm": "^1.6.1" - } - }, - "@jimp/utils": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.14.0.tgz", - "integrity": "sha512-MY5KFYUru0y74IsgM/9asDwb3ERxWxXEu3CRCZEvE7DtT86y1bR1XgtlSliMrptjz4qbivNGMQSvUBpEFJDp1A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "regenerator-runtime": "^0.13.3" - } - }, - "@wdio/config": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-6.8.0.tgz", - "integrity": "sha512-q71VJpa50iz9Bq0QoZIT+egZIAQGiv6mRcmqub8/R318XL35Y6NKHJIOyHl8xVmHwitYy//fawZou2nYjU66KQ==", - "dev": true, - "requires": { - "@wdio/logger": "6.8.0", - "deepmerge": "^4.0.0", - "glob": "^7.1.2" - } - }, - "@wdio/logger": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-6.8.0.tgz", - "integrity": "sha512-IvRnp2gTU1z6L+snMrKLrRDqYFq9yzcqXp7i6+Q/bxewxkgcpitm4hSs+13KS4fmbeBmhT5UeUeumnTZBYkhBQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - } - }, - "@wdio/protocols": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-6.8.0.tgz", - "integrity": "sha512-A9k3DYBxt220SK57LlALscHd/4KUa6kzJdc4UJ84Dyylymmjhs3Uau9WL37yyMMd6Y/5sSfUNRrAUEDZnmOzyQ==", - "dev": true - }, - "@wdio/repl": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-6.8.0.tgz", - "integrity": "sha512-unFnItXq6+V8JNfAtPtuEza047r2dLdcFXPN4exq7+O/kPJTzsTGOAQTlSLPJGMrfy5axTk90KOl08gpJvzjOA==", - "dev": true, - "requires": { - "@wdio/utils": "6.8.0" - } - }, - "@wdio/utils": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-6.8.0.tgz", - "integrity": "sha512-2vGwkaqP2e876o3NDTWz021aLTBrbZfCLHETuS+e/J0IXMR3FQ8et01BY/bjwyz6EP1I3vVtP2ZVC1dV2yIIVQ==", - "dev": true, - "requires": { - "@wdio/logger": "6.8.0" - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "appium-base-driver": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/appium-base-driver/-/appium-base-driver-5.8.1.tgz", - "integrity": "sha512-k5ybExgP0kJx7vsR0Y8GeEay+Vr0yCs0muzYtdrIDbqOCz5bFX0skyZubSSsm/lOE4KvgHhm4cRwWJM0DlHvRA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "appium-support": "^2.46.0", - "async-lock": "^1.0.0", - "asyncbox": "^2.3.1", - "axios": "^0.19.2", - "bluebird": "^3.5.3", - "body-parser": "^1.18.2", - "colors": "^1.1.2", - "es6-error": "^4.1.1", - "express": "^4.16.2", - "http-status-codes": "^1.3.0", - "lodash": "^4.0.0", - "lru-cache": "^5.0.0", - "method-override": "^3.0.0", - "morgan": "^1.9.0", - "request": "^2.88.2", - "request-promise": "^4.2.5", - "serve-favicon": "^2.4.5", - "source-map-support": "^0.5.5", - "validate.js": "^0.13.0", - "webdriverio": "^6.0.2", - "ws": "^7.0.0" - }, - "dependencies": { - "appium-support": { - "version": "2.49.0", - "resolved": "https://registry.npmjs.org/appium-support/-/appium-support-2.49.0.tgz", - "integrity": "sha512-PtUsfpCjdGqZTTuRyH5U9iOGq9SpEVHg0H6/2HrazkX6ZvSQ+kIVYHTAGIXVeBJecfN9/syqgMmEekVgD15BGA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "archiver": "^5.0.0", - "axios": "^0.20.0", - "base64-stream": "^1.0.0", - "bluebird": "^3.5.1", - "bplist-creator": "^0", - "bplist-parser": "^0.2", - "form-data": "^3.0.0", - "get-stream": "^6.0.0", - "glob": "^7.1.2", - "jimp": "^0.14.0", - "jsftp": "^2.1.2", - "klaw": "^3.0.0", - "lockfile": "^1.0.4", - "lodash": "^4.2.1", - "mjpeg-server": "^0.3.0", - "mkdirp": "^1.0.0", - "moment": "^2.24.0", - "mv": "^2.1.1", - "ncp": "^2.0.0", - "npmlog": "^4.1.2", - "plist": "^3.0.1", - "pluralize": "^8.0.0", - "pngjs": "^5.0.0", - "rimraf": "^3.0.0", - "sanitize-filename": "^1.6.1", - "semver": "^7.0.0", - "shell-quote": "^1.7.2", - "source-map-support": "^0.5.5", - "teen_process": "^1.5.1", - "uuid": "^8.0.0", - "which": "^2.0.0", - "yauzl": "^2.7.0" - }, - "dependencies": { - "axios": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", - "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", - "dev": true, - "requires": { - "follow-redirects": "^1.10.0" - } - } - } - } - } - }, - "archiver": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.0.2.tgz", - "integrity": "sha512-Tq3yV/T4wxBsD2Wign8W9VQKhaUxzzRmjEiSoOK0SLqPgDP/N1TKdYyBeIEu56T4I9iO4fKTTR0mN9NWkBA0sg==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "async": "^3.2.0", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", - "tar-stream": "^2.1.4", - "zip-stream": "^4.0.0" - } - }, - "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dev": true, - "requires": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } - } - }, - "async": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", - "dev": true - }, - "bl": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "compress-commons": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.0.1.tgz", - "integrity": "sha512-xZm9o6iikekkI0GnXCmAl3LQGZj5TBDj0zLowsqi7tJtEa3FMGSEcHcqrSJIrOAk1UG/NBbDn/F1q+MG/p/EsA==", - "dev": true, - "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.0", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - } - }, - "crc32-stream": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.0.tgz", - "integrity": "sha512-tyMw2IeUX6t9jhgXI6um0eKfWq4EIDpfv5m7GX4Jzp7eVelQ360xd8EPXJhp2mHwLQIkqlnMLjzqSZI3a+0wRw==", - "dev": true, - "requires": { - "crc": "^3.4.4", - "readable-stream": "^3.4.0" - } - }, - "devtools": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-6.8.0.tgz", - "integrity": "sha512-DaXkQP312NclQPzSDHD3wvenglkxUDKiQwjlEqFvOXc+mc4o10WljVUx6B3Q0Ig7YmgBHsKREcNFYAwXTnzT7Q==", - "dev": true, - "requires": { - "@types/puppeteer-core": "^2.0.0", - "@types/ua-parser-js": "^0.7.33", - "@types/uuid": "^8.3.0", - "@wdio/config": "6.8.0", - "@wdio/logger": "6.8.0", - "@wdio/protocols": "6.8.0", - "@wdio/utils": "6.8.0", - "chrome-launcher": "^0.13.1", - "edge-paths": "^2.1.0", - "puppeteer-core": "^5.1.0", - "ua-parser-js": "^0.7.21", - "uuid": "^8.0.0" - } - }, - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - } - } - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "follow-redirects": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", - "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", - "dev": true - }, - "form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", - "dev": true - }, - "jimp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.14.0.tgz", - "integrity": "sha512-8BXU+J8+SPmwwyq9ELihpSV4dWPTiOKBWCEgtkbnxxAVMjXdf3yGmyaLSshBfXc8sP/JQ9OZj5R8nZzz2wPXgA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/custom": "^0.14.0", - "@jimp/plugins": "^0.14.0", - "@jimp/types": "^0.14.0", - "regenerator-runtime": "^0.13.3" - } - }, - "jpeg-js": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.2.tgz", - "integrity": "sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw==", - "dev": true - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "pngjs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", - "dev": true - }, - "puppeteer-core": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-5.5.0.tgz", - "integrity": "sha512-tlA+1n+ziW/Db03hVV+bAecDKse8ihFRXYiEypBe9IlLRvOCzYFG6qrCMBYK34HO/Q/Ecjc+tvkHRAfLVH+NgQ==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.818844", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^4.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.0.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "resq": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/resq/-/resq-1.9.2.tgz", - "integrity": "sha512-Y+fprJ9wQY64gh+vJRNatiG61G+9XD5jJe4kI/Rqw6gmOa5ihZvgrxZVydqyM96xj75jwaRCPVYPU3RwsEk6ug==", + "@babel/runtime": "^7.7.2", + "@jimp/bmp": "^0.10.3", + "@jimp/gif": "^0.10.3", + "@jimp/jpeg": "^0.10.3", + "@jimp/png": "^0.10.3", + "@jimp/tiff": "^0.10.3", + "core-js": "^3.4.1", + "timm": "^1.6.1" + } + }, + "@jimp/utils": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.10.3.tgz", + "integrity": "sha512-VcSlQhkil4ReYmg1KkN+WqHyYfZ2XfZxDsKAHSfST1GEz/RQHxKZbX+KhFKtKflnL0F4e6DlNQj3vznMNXCR2w==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.2", + "core-js": "^3.4.1", + "regenerator-runtime": "^0.13.3" + } + }, + "@sindresorhus/is": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-3.0.0.tgz", + "integrity": "sha512-kqA5I6Yun7PBHk8WN9BBP1c7FfN2SrD05GuVSEYPqDb4nerv7HqYfgBfMIKmT/EuejURkJKLZuLyGKGs6WEG9w==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", + "dev": true, + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "@types/cacheable-request": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "dev": true, + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", + "dev": true + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/http-cache-semantics": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", + "dev": true + }, + "@types/keyv": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "14.0.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz", + "integrity": "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw==", + "dev": true + }, + "@types/request": { + "version": "2.48.5", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz", + "integrity": "sha512-/LO7xRVnL3DxJ1WkPGDQrp4VTV1reX9RkC85mJ+Qzykj2Bdw+mG15aAfDahc76HtknjzE16SX/Yddn6MxVbmGQ==", + "dev": true, + "requires": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + }, + "dependencies": { + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", "dev": true, "requires": { - "fast-deep-equal": "^2.0.1" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" } - }, - "rgb2hex": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.0.tgz", - "integrity": "sha512-cHdNTwmTMPu/TpP1bJfdApd6MbD+Kzi4GNnM6h35mdFChhQPSi9cAI8J7DMn5kQDKX8NuBaQXAyo360Oa7tOEA==", + } + } + }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==", + "dev": true + }, + "@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "@wdio/config": { + "version": "6.1.14", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-6.1.14.tgz", + "integrity": "sha512-MXHMHwtkAblfnIxONs9aW//T9Fq5XIw3oH+tztcBRvNTTAIXmwHd+4sOjAwjpCdBSGs0C4kM/aTpGfwDZVURvQ==", + "dev": true, + "requires": { + "@wdio/logger": "6.0.16", + "deepmerge": "^4.0.0", + "glob": "^7.1.2" + } + }, + "@wdio/logger": { + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-6.0.16.tgz", + "integrity": "sha512-VbH5UnQIG/3sSMV+Y38+rOdwyK9mVA9vuL7iOngoTafHwUjL1MObfN/Cex84L4mGxIgfxCu6GV48iUmSuQ7sqA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, - "serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", - "dev": true, - "requires": { - "type-fest": "^0.13.1" - } - }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -22861,520 +15398,433 @@ "requires": { "ansi-regex": "^5.0.0" } - }, - "tar-stream": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", - "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", - "dev": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true - }, - "uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", + } + } + }, + "@wdio/protocols": { + "version": "6.1.25", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-6.1.25.tgz", + "integrity": "sha512-C84qqh5J6nE1zTjwF3svDUah3FvoUzMUGeRe8w//QPBcJIGNRgmVLgeFV7Cp2EwI5ag+BUfXExQ0bFtcZYHIVA==", + "dev": true + }, + "@wdio/repl": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-6.2.0.tgz", + "integrity": "sha512-63yuCBCLrTC1D5uk6wJ6D3lyhi5i3Dvj8sQuqSma66c+xYPQRNA/Jp6V+43CsnOzUDLvhliSjCanrl3/i2sOcg==", + "dev": true, + "requires": { + "@wdio/utils": "6.2.0" + } + }, + "@wdio/utils": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-6.2.0.tgz", + "integrity": "sha512-nPx430WPtZts09pcFwkTMO3zm6F7qvPuOcbvCschSW6ay8ebBvS6lkKnAHxCjrslsIF8+lOESuF0QrWycoouRA==", + "dev": true, + "requires": { + "@wdio/logger": "6.0.16" + } + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "adbkit-apkreader": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/adbkit-apkreader/-/adbkit-apkreader-3.2.0.tgz", + "integrity": "sha512-QwsxPYCqWSmCAiW/A4gq0eytb4jtZc7WNbECIhLCRfGEB38oXzIV/YkTpkOTQFKSg3S4Svb6y///qOUH7UrWWw==", + "dev": true, + "requires": { + "bluebird": "^3.4.7", + "debug": "~4.1.1", + "yauzl": "^2.7.0" + } + }, + "agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true + }, + "aggregate-error": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", + "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "dependencies": { + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true - }, - "webdriver": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-6.8.0.tgz", - "integrity": "sha512-KijngSC7ZjgdDoFuJO70StImWB3qiiLbvURiTNQ/oeQaP0CnerQX5qvYpgMnP1CZNhxyyUbb+1+w0zBqSNfWaA==", - "dev": true, - "requires": { - "@types/lodash.merge": "^4.6.6", - "@wdio/config": "6.8.0", - "@wdio/logger": "6.8.0", - "@wdio/protocols": "6.8.0", - "@wdio/utils": "6.8.0", - "got": "^11.0.2", - "lodash.merge": "^4.6.1" - } - }, - "webdriverio": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-6.8.0.tgz", - "integrity": "sha512-jVN73xLUcIF3PJYgeqzTbyak0QfqUegq/hjaIkLl8wQWRNubuEA3dN9L4U33AzOokYvcBXinemh3Ein35f1a8A==", - "dev": true, - "requires": { - "@types/archiver": "^3.1.1", - "@types/atob": "^2.1.2", - "@types/fs-extra": "^9.0.2", - "@types/lodash.clonedeep": "^4.5.6", - "@types/lodash.isplainobject": "^4.0.6", - "@types/puppeteer-core": "^2.0.0", - "@wdio/config": "6.8.0", - "@wdio/logger": "6.8.0", - "@wdio/repl": "6.8.0", - "@wdio/utils": "6.8.0", - "archiver": "^5.0.0", - "atob": "^2.1.2", - "css-value": "^0.0.1", - "devtools": "6.8.0", - "fs-extra": "^9.0.1", - "get-port": "^5.1.1", - "grapheme-splitter": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", - "lodash.zip": "^4.2.0", - "minimatch": "^3.0.4", - "puppeteer-core": "^5.1.0", - "resq": "^1.9.1", - "rgb2hex": "^0.2.0", - "serialize-error": "^7.0.0", - "webdriver": "6.8.0" - } - }, - "zip-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.0.2.tgz", - "integrity": "sha512-TGxB2g+1ur6MHkvM644DuZr8Uzyz0k0OYWtS3YlpfWBEmK4woaC2t3+pozEL3dBfIPmpgmClR5B2QRcMgGt22g==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.0.0", - "readable-stream": "^3.6.0" - } } } }, - "appium-remote-debugger": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/appium-remote-debugger/-/appium-remote-debugger-8.9.0.tgz", - "integrity": "sha512-GSq6luI9Us6urS7K8c90R13/MU1T95GGKNBV4t+IPw7cvDcm5fpsnqKX77xrf+7z/n4claUk/8m6yce9sSskqQ==", + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, + "any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", + "dev": true + }, + "appium-adb": { + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/appium-adb/-/appium-adb-8.6.2.tgz", + "integrity": "sha512-jAvyjAGtAb/XzUOZ/vQzKQhn17z2ooqHKfi8armU260jHSj4q/fuILkNoVC9r6sCwn8pzaX4FmikEMxqYOJiMg==", "dev": true, "requires": { "@babel/runtime": "^7.0.0", - "appium-base-driver": "^5.0.0", - "appium-ios-device": "^1.2.1", - "appium-support": "^2.41.0", - "async-lock": "^1.2.2", + "adbkit-apkreader": "^3.1.2", + "appium-support": "^2.42.0", + "async-lock": "^1.0.0", "asyncbox": "^2.6.0", "bluebird": "^3.4.7", + "lodash": "^4.0.0", + "lru-cache": "^5.0.0", + "semver": "^7.0.0", + "source-map-support": "^0.5.5", + "teen_process": "^1.11.0", + "utf7": "^1.0.2" + } + }, + "appium-android-driver": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/appium-android-driver/-/appium-android-driver-4.37.0.tgz", + "integrity": "sha512-jDZ4mmpdj2/cAFsmnoB8uHhrVKicw9Fz4ginS1EMFwfl99KxfTHaHvIM8DuhIE4SaDc2guccIq8mTmJnWmwfAw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.0.0", + "appium-adb": "^8.1.0", + "appium-base-driver": "^6.2.0", + "appium-chromedriver": "^4.13.0", + "appium-support": "^2.47.1", + "async-lock": "^1.2.2", + "asyncbox": "^2.0.4", + "axios": "^0.19.2", + "bluebird": "^3.4.7", + "io.appium.settings": "^3.1.0", + "jimp": "^0.10.0", + "lodash": "^4.17.4", + "lru-cache": "^5.1.1", + "moment": "^2.24.0", + "moment-timezone": "^0.5.26", + "portfinder": "^1.0.6", + "portscanner": "2.2.0", + "semver": "^7.0.0", + "shared-preferences-builder": "^0.0.4", + "source-map-support": "^0.5.5", + "teen_process": "^1.9.0", + "ws": "^7.0.0", + "yargs": "^15.0.1" + } + }, + "appium-base-driver": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/appium-base-driver/-/appium-base-driver-6.2.3.tgz", + "integrity": "sha512-kSUSIcoBF7pXLn1C5lLjQfIAiS56VQ62AlK5x9Wkjuynj0DNTc2OgGVv+xwRbxAUe4MiRclPy/WytccDwastqg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.0.0", + "appium-support": "^2.48.0", + "async-lock": "^1.0.0", + "asyncbox": "^2.3.1", + "axios": "^0.19.2", + "bluebird": "^3.5.3", + "body-parser": "^1.18.2", + "colors": "^1.1.2", + "es6-error": "^4.1.1", + "express": "^4.16.2", + "http-status-codes": "^1.3.0", + "lodash": "^4.0.0", + "lru-cache": "^5.0.0", + "method-override": "^3.0.0", + "morgan": "^1.9.0", + "serve-favicon": "^2.4.5", + "source-map-support": "^0.5.5", + "validate.js": "^0.13.0", + "webdriverio": "^6.0.2", + "ws": "^7.0.0" + } + }, + "appium-chromedriver": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/appium-chromedriver/-/appium-chromedriver-4.25.1.tgz", + "integrity": "sha512-yK8+9U8MTsgrjn+X0NHVp6UXEjCxXSm4VuJZwxK6FOzguoZHIyKYOEVrYqMirJ3Br+YqCmVD+H82u0LVCtSqNQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.0.0", + "appium-base-driver": "^6.0.0", + "appium-support": "^2.46.0", + "asyncbox": "^2.0.2", + "axios": "^0.19.2", + "bluebird": "^3.5.1", + "compare-versions": "^3.4.0", + "fancy-log": "^1.3.2", + "lodash": "^4.17.4", + "semver": "^7.0.0", + "source-map-support": "^0.5.5", + "teen_process": "^1.15.0", + "xmldom": "^0.3.0", + "xpath": "^0.0.27" + }, + "dependencies": { + "xmldom": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.3.0.tgz", + "integrity": "sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g==", + "dev": true + } + } + }, + "appium-espresso-driver": { + "version": "1.31.0", + "resolved": "https://registry.npmjs.org/appium-espresso-driver/-/appium-espresso-driver-1.31.0.tgz", + "integrity": "sha512-l4K2ZWUYeh2g8JcDcoUF98gTNDbBvgPfi2aVmJW4PkGAxXbnyHYt/5beRIUk0zC0sj0SgnjUwSkD+ewYg8AeSg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.4.3", + "appium-adb": "^8.0.0", + "appium-android-driver": "^4.37.0", + "appium-base-driver": "^6.0.1", + "appium-support": "^2.46.0", + "asyncbox": "^2.3.1", + "bluebird": "^3.5.0", "lodash": "^4.17.11", - "source-map-support": "^0.5.5" + "portscanner": "^2.1.1", + "source-map-support": "^0.5.8", + "validate.js": "^0.13.0", + "yargs": "^15.0.1" + } + }, + "appium-fake-driver": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/appium-fake-driver/-/appium-fake-driver-1.0.0.tgz", + "integrity": "sha512-pvRAGi4efTNhX01gE2jPxi/hmdKHO072Zim0fuZXrcTku5nuinh6ZMIJzju0PC/tuQmtfWR2a/N4fc8u6hSq8Q==", + "dev": true, + "requires": { + "@babel/runtime": "^7.0.0", + "appium-base-driver": "^6.0.1", + "appium-support": "^2.11.1", + "asyncbox": "^2.3.2", + "bluebird": "^3.5.1", + "lodash": "^4.17.4", + "source-map-support": "^0.5.5", + "xmldom": "^0.3.0", + "xpath": "0.0.27", + "yargs": "^15.0.1" }, "dependencies": { - "@jimp/bmp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.14.0.tgz", - "integrity": "sha512-5RkX6tSS7K3K3xNEb2ygPuvyL9whjanhoaB/WmmXlJS6ub4DjTqrapu8j4qnIWmO4YYtFeTbDTXV6v9P1yMA5A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "bmp-js": "^0.1.0" - } - }, - "@jimp/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.14.0.tgz", - "integrity": "sha512-S62FcKdtLtj3yWsGfJRdFXSutjvHg7aQNiFogMbwq19RP4XJWqS2nOphu7ScB8KrSlyy5nPF2hkWNhLRLyD82w==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "any-base": "^1.1.0", - "buffer": "^5.2.0", - "exif-parser": "^0.1.12", - "file-type": "^9.0.0", - "load-bmfont": "^1.3.1", - "mkdirp": "^0.5.1", - "phin": "^2.9.1", - "pixelmatch": "^4.0.2", - "tinycolor2": "^1.4.1" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "@jimp/custom": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.14.0.tgz", - "integrity": "sha512-kQJMeH87+kWJdVw8F9GQhtsageqqxrvzg7yyOw3Tx/s7v5RToe8RnKyMM+kVtBJtNAG+Xyv/z01uYQ2jiZ3GwA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/core": "^0.14.0" - } - }, - "@jimp/gif": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.14.0.tgz", - "integrity": "sha512-DHjoOSfCaCz72+oGGEh8qH0zE6pUBaBxPxxmpYJjkNyDZP7RkbBkZJScIYeQ7BmJxmGN4/dZn+MxamoQlr+UYg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "gifwrap": "^0.9.2", - "omggif": "^1.0.9" - } - }, - "@jimp/jpeg": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.14.0.tgz", - "integrity": "sha512-561neGbr+87S/YVQYnZSTyjWTHBm9F6F1obYHiyU3wVmF+1CLbxY3FQzt4YolwyQHIBv36Bo0PY2KkkU8BEeeQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "jpeg-js": "^0.4.0" - } - }, - "@jimp/plugin-blit": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.14.0.tgz", - "integrity": "sha512-YoYOrnVHeX3InfgbJawAU601iTZMwEBZkyqcP1V/S33Qnz9uzH1Uj1NtC6fNgWzvX6I4XbCWwtr4RrGFb5CFrw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-blur": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.14.0.tgz", - "integrity": "sha512-9WhZcofLrT0hgI7t0chf7iBQZib//0gJh9WcQMUt5+Q1Bk04dWs8vTgLNj61GBqZXgHSPzE4OpCrrLDBG8zlhQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-circle": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.14.0.tgz", - "integrity": "sha512-o5L+wf6QA44tvTum5HeLyLSc5eVfIUd5ZDVi5iRfO4o6GT/zux9AxuTSkKwnjhsG8bn1dDmywAOQGAx7BjrQVA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-color": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.14.0.tgz", - "integrity": "sha512-JJz512SAILYV0M5LzBb9sbOm/XEj2fGElMiHAxb7aLI6jx+n0agxtHpfpV/AePTLm1vzzDxx6AJxXbKv355hBQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "tinycolor2": "^1.4.1" - } - }, - "@jimp/plugin-contain": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.14.0.tgz", - "integrity": "sha512-RX2q233lGyaxiMY6kAgnm9ScmEkNSof0hdlaJAVDS1OgXphGAYAeSIAwzESZN4x3ORaWvkFefeVH9O9/698Evg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-cover": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.14.0.tgz", - "integrity": "sha512-0P/5XhzWES4uMdvbi3beUgfvhn4YuQ/ny8ijs5kkYIw6K8mHcl820HahuGpwWMx56DJLHRl1hFhJwo9CeTRJtQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-crop": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.14.0.tgz", - "integrity": "sha512-Ojtih+XIe6/XSGtpWtbAXBozhCdsDMmy+THUJAGu2x7ZgKrMS0JotN+vN2YC3nwDpYkM+yOJImQeptSfZb2Sug==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-displace": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.14.0.tgz", - "integrity": "sha512-c75uQUzMgrHa8vegkgUvgRL/PRvD7paFbFJvzW0Ugs8Wl+CDMGIPYQ3j7IVaQkIS+cAxv+NJ3TIRBQyBrfVEOg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-dither": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.14.0.tgz", - "integrity": "sha512-g8SJqFLyYexXQQsoh4dc1VP87TwyOgeTElBcxSXX2LaaMZezypmxQfLTzOFzZoK8m39NuaoH21Ou1Ftsq7LzVQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-fisheye": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.14.0.tgz", - "integrity": "sha512-BFfUZ64EikCaABhCA6mR3bsltWhPpS321jpeIQfJyrILdpFsZ/OccNwCgpW1XlbldDHIoNtXTDGn3E+vCE7vDg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-flip": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.14.0.tgz", - "integrity": "sha512-WtL1hj6ryqHhApih+9qZQYA6Ye8a4HAmdTzLbYdTMrrrSUgIzFdiZsD0WeDHpgS/+QMsWwF+NFmTZmxNWqKfXw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-gaussian": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.14.0.tgz", - "integrity": "sha512-uaLwQ0XAQoydDlF9tlfc7iD9drYPriFe+jgYnWm8fbw5cN+eOIcnneEX9XCOOzwgLPkNCxGox6Kxjn8zY6GxtQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-invert": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.14.0.tgz", - "integrity": "sha512-UaQW9X9vx8orQXYSjT5VcITkJPwDaHwrBbxxPoDG+F/Zgv4oV9fP+udDD6qmkgI9taU+44Fy+zm/J/gGcMWrdg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-mask": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.14.0.tgz", - "integrity": "sha512-tdiGM69OBaKtSPfYSQeflzFhEpoRZ+BvKfDEoivyTjauynbjpRiwB1CaiS8En1INTDwzLXTT0Be9SpI3LkJoEA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-normalize": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.14.0.tgz", - "integrity": "sha512-AfY8sqlsbbdVwFGcyIPy5JH/7fnBzlmuweb+Qtx2vn29okq6+HelLjw2b+VT2btgGUmWWHGEHd86oRGSoWGyEQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-print": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.14.0.tgz", - "integrity": "sha512-MwP3sH+VS5AhhSTXk7pui+tEJFsxnTKFY3TraFJb8WFbA2Vo2qsRCZseEGwpTLhENB7p/JSsLvWoSSbpmxhFAQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "load-bmfont": "^1.4.0" - } - }, - "@jimp/plugin-resize": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.14.0.tgz", - "integrity": "sha512-qFeMOyXE/Bk6QXN0GQo89+CB2dQcXqoxUcDb2Ah8wdYlKqpi53skABkgVy5pW3EpiprDnzNDboMltdvDslNgLQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-rotate": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.14.0.tgz", - "integrity": "sha512-aGaicts44bvpTcq5Dtf93/8TZFu5pMo/61lWWnYmwJJU1RqtQlxbCLEQpMyRhKDNSfPbuP8nyGmaqXlM/82J0Q==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-scale": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.14.0.tgz", - "integrity": "sha512-ZcJk0hxY5ZKZDDwflqQNHEGRblgaR+piePZm7dPwPUOSeYEH31P0AwZ1ziceR74zd8N80M0TMft+e3Td6KGBHw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-shadow": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.14.0.tgz", - "integrity": "sha512-p2igcEr/iGrLiTu0YePNHyby0WYAXM14c5cECZIVnq/UTOOIQ7xIcWZJ1lRbAEPxVVXPN1UibhZAbr3HAb5BjQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-threshold": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.14.0.tgz", - "integrity": "sha512-N4BlDgm/FoOMV/DQM2rSpzsgqAzkP0DXkWZoqaQrlRxQBo4zizQLzhEL00T/YCCMKnddzgEhnByaocgaaa0fKw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugins": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.14.0.tgz", - "integrity": "sha512-vDO3XT/YQlFlFLq5TqNjQkISqjBHT8VMhpWhAfJVwuXIpilxz5Glu4IDLK6jp4IjPR6Yg2WO8TmRY/HI8vLrOw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/plugin-blit": "^0.14.0", - "@jimp/plugin-blur": "^0.14.0", - "@jimp/plugin-circle": "^0.14.0", - "@jimp/plugin-color": "^0.14.0", - "@jimp/plugin-contain": "^0.14.0", - "@jimp/plugin-cover": "^0.14.0", - "@jimp/plugin-crop": "^0.14.0", - "@jimp/plugin-displace": "^0.14.0", - "@jimp/plugin-dither": "^0.14.0", - "@jimp/plugin-fisheye": "^0.14.0", - "@jimp/plugin-flip": "^0.14.0", - "@jimp/plugin-gaussian": "^0.14.0", - "@jimp/plugin-invert": "^0.14.0", - "@jimp/plugin-mask": "^0.14.0", - "@jimp/plugin-normalize": "^0.14.0", - "@jimp/plugin-print": "^0.14.0", - "@jimp/plugin-resize": "^0.14.0", - "@jimp/plugin-rotate": "^0.14.0", - "@jimp/plugin-scale": "^0.14.0", - "@jimp/plugin-shadow": "^0.14.0", - "@jimp/plugin-threshold": "^0.14.0", - "timm": "^1.6.1" - } - }, - "@jimp/png": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.14.0.tgz", - "integrity": "sha512-0RV/mEIDOrPCcNfXSPmPBqqSZYwGADNRVUTyMt47RuZh7sugbYdv/uvKmQSiqRdR0L1sfbCBMWUEa5G/8MSbdA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "pngjs": "^3.3.3" - } - }, - "@jimp/tiff": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.14.0.tgz", - "integrity": "sha512-zBYDTlutc7j88G/7FBCn3kmQwWr0rmm1e0FKB4C3uJ5oYfT8645lftUsvosKVUEfkdmOaMAnhrf4ekaHcb5gQw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "utif": "^2.0.1" - } - }, - "@jimp/types": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.14.0.tgz", - "integrity": "sha512-hx3cXAW1KZm+b+XCrY3LXtdWy2U+hNtq0rPyJ7NuXCjU7lZR3vIkpz1DLJ3yDdS70hTi5QDXY3Cd9kd6DtloHQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/bmp": "^0.14.0", - "@jimp/gif": "^0.14.0", - "@jimp/jpeg": "^0.14.0", - "@jimp/png": "^0.14.0", - "@jimp/tiff": "^0.14.0", - "timm": "^1.6.1" - } - }, - "@jimp/utils": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.14.0.tgz", - "integrity": "sha512-MY5KFYUru0y74IsgM/9asDwb3ERxWxXEu3CRCZEvE7DtT86y1bR1XgtlSliMrptjz4qbivNGMQSvUBpEFJDp1A==", + "xmldom": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.3.0.tgz", + "integrity": "sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g==", + "dev": true + } + } + }, + "appium-flutter-driver": { + "version": "0.0.23", + "resolved": "https://registry.npmjs.org/appium-flutter-driver/-/appium-flutter-driver-0.0.23.tgz", + "integrity": "sha512-yRZwfioaMpE2s9Av8pgv6hQkArsQ36KkrEgHaOk5uRRuTXbHSg0OB51Bm8xKdXvWYmA4DI6Oz4xWSTNjfdrJhw==", + "dev": true, + "requires": { + "appium-base-driver": "^5.0.0", + "appium-uiautomator2-driver": "^1.35.1", + "appium-xcuitest-driver": "^3.0.0", + "rpc-websockets": "^4.5.1" + }, + "dependencies": { + "appium-base-driver": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/appium-base-driver/-/appium-base-driver-5.8.1.tgz", + "integrity": "sha512-k5ybExgP0kJx7vsR0Y8GeEay+Vr0yCs0muzYtdrIDbqOCz5bFX0skyZubSSsm/lOE4KvgHhm4cRwWJM0DlHvRA==", "dev": true, "requires": { - "@babel/runtime": "^7.7.2", - "regenerator-runtime": "^0.13.3" + "@babel/runtime": "^7.0.0", + "appium-support": "^2.46.0", + "async-lock": "^1.0.0", + "asyncbox": "^2.3.1", + "axios": "^0.19.2", + "bluebird": "^3.5.3", + "body-parser": "^1.18.2", + "colors": "^1.1.2", + "es6-error": "^4.1.1", + "express": "^4.16.2", + "http-status-codes": "^1.3.0", + "lodash": "^4.0.0", + "lru-cache": "^5.0.0", + "method-override": "^3.0.0", + "morgan": "^1.9.0", + "request": "^2.88.2", + "request-promise": "^4.2.5", + "serve-favicon": "^2.4.5", + "source-map-support": "^0.5.5", + "validate.js": "^0.13.0", + "webdriverio": "^6.0.2", + "ws": "^7.0.0" } - }, + } + } + }, + "appium-idb": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/appium-idb/-/appium-idb-0.5.0.tgz", + "integrity": "sha512-6IayLxBbN/2fOzbxjpOt/ntoFlFfcyZbURKyeH9XJyAjjN5iIG+i8EWGof6tMM5BHDC1mOJcPZkFaQtYi/Y5Iw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.0.0", + "appium-support": "^2.41.0", + "asyncbox": "^2.5.2", + "bluebird": "^3.1.1", + "lodash": "^4.0.0", + "source-map-support": "^0.5.5", + "teen_process": "^1.11.0" + } + }, + "appium-ios-device": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/appium-ios-device/-/appium-ios-device-1.7.0.tgz", + "integrity": "sha512-Xx4kXh0/17leaPXW2IYqDn4AJOpCnvmQJmRKBW0hiUR6osNk3CzrSFXPR98IDP3So8qnh8oM5gdpudQC8j3J4Q==", + "dev": true, + "requires": { + "@babel/runtime": "^7.0.0", + "appium-support": "^2.35.0", + "bluebird": "^3.1.1", + "lodash": "^4.17.15", + "semver": "^7.0.0", + "source-map-support": "^0.5.5" + } + }, + "appium-ios-driver": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/appium-ios-driver/-/appium-ios-driver-4.7.0.tgz", + "integrity": "sha512-2MSJZRP7L2SLdcLPvMNSaZKuXWiJA4eCFIVpli0H/cO0ZBSC/YZ2cCYmSHzH3RimbRETGll1Nzolt6oHMaz9JA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.0.0", + "appium-base-driver": "^6.0.1", + "appium-ios-simulator": "^3.21.0", + "appium-remote-debugger": "^4.1.0", + "appium-support": "^2.41.0", + "appium-xcode": "^3.1.0", + "asyncbox": "^2.3.1", + "axios": "^0.19.2", + "bluebird": "^3.5.1", + "colors": "^1.1.2", + "js2xmlparser2": "^0.2.0", + "lodash": "^4.13.1", + "moment": "^2.24.0", + "moment-timezone": "^0.5.26", + "node-idevice": "^0.1.6", + "pem": "^1.8.3", + "portfinder": "^1.0.13", + "safari-launcher": "^2.0.5", + "source-map-support": "^0.5.5", + "teen_process": "^1.6.0", + "through": "^2.3.8", + "xmldom": "^0.3.0", + "xpath": "^0.0.24", + "yargs": "^15.0.1" + }, + "dependencies": { "@wdio/config": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-6.8.0.tgz", - "integrity": "sha512-q71VJpa50iz9Bq0QoZIT+egZIAQGiv6mRcmqub8/R318XL35Y6NKHJIOyHl8xVmHwitYy//fawZou2nYjU66KQ==", + "version": "5.22.4", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-5.22.4.tgz", + "integrity": "sha512-i5dJQWb80darcRA//tfG0guMeQCeRUXroZNnHjGNb1qzvTRZmcIIhdxaD+DbK/5dWEx6aoMfoi6wjVp/CXwdAg==", "dev": true, "requires": { - "@wdio/logger": "6.8.0", + "@wdio/logger": "5.16.10", "deepmerge": "^4.0.0", "glob": "^7.1.2" } }, "@wdio/logger": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-6.8.0.tgz", - "integrity": "sha512-IvRnp2gTU1z6L+snMrKLrRDqYFq9yzcqXp7i6+Q/bxewxkgcpitm4hSs+13KS4fmbeBmhT5UeUeumnTZBYkhBQ==", + "version": "5.16.10", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-5.16.10.tgz", + "integrity": "sha512-hRKhxgd9uB48Dtj2xe2ckxU4KwI/RO8IwguySuaI2SLFj6EDbdonwzpVkq111/fjBuq7R1NauAaNcm3AMEbIFA==", "dev": true, "requires": { - "chalk": "^4.0.0", + "chalk": "^3.0.0", "loglevel": "^1.6.0", "loglevel-plugin-prefix": "^0.8.4", "strip-ansi": "^6.0.0" } }, "@wdio/protocols": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-6.8.0.tgz", - "integrity": "sha512-A9k3DYBxt220SK57LlALscHd/4KUa6kzJdc4UJ84Dyylymmjhs3Uau9WL37yyMMd6Y/5sSfUNRrAUEDZnmOzyQ==", + "version": "5.22.1", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-5.22.1.tgz", + "integrity": "sha512-GdoWb/HTrb09Qb0S/7sLp1NU94LAhTsF1NnFj5sEFSUpecrl0S07pnhVg54pUImectN/woaqSl7uJGjlSGZcdQ==", "dev": true }, "@wdio/repl": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-6.8.0.tgz", - "integrity": "sha512-unFnItXq6+V8JNfAtPtuEza047r2dLdcFXPN4exq7+O/kPJTzsTGOAQTlSLPJGMrfy5axTk90KOl08gpJvzjOA==", + "version": "5.23.0", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-5.23.0.tgz", + "integrity": "sha512-cKG9m0XuqcQenQmoup0yJX1fkDQEdY06QXuwt636ZQf6XgDoeoAdNOgnRnNruQ0+JsC2eqHFoSNto1q8wcLH/g==", "dev": true, "requires": { - "@wdio/utils": "6.8.0" + "@wdio/utils": "5.23.0" } }, "@wdio/utils": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-6.8.0.tgz", - "integrity": "sha512-2vGwkaqP2e876o3NDTWz021aLTBrbZfCLHETuS+e/J0IXMR3FQ8et01BY/bjwyz6EP1I3vVtP2ZVC1dV2yIIVQ==", + "version": "5.23.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-5.23.0.tgz", + "integrity": "sha512-dWPEkDiaNUqJXPO6L2di2apI7Rle7Er4euh67Wlb5+3MrPNjCKhiF8gHcpQeL8oe6A1MH/f89kpSEEXe4BMkAw==", "dev": true, "requires": { - "@wdio/logger": "6.8.0" + "@wdio/logger": "5.16.10", + "deepmerge": "^4.0.0" } }, "ansi-regex": { @@ -23383,135 +15833,103 @@ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, - "appium-base-driver": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/appium-base-driver/-/appium-base-driver-7.3.0.tgz", - "integrity": "sha512-CVrpKPrch52KIDgXjMXfKs0/55R3X8TRkyu7xcun1cjl6nYlMb+wpoDCdlRmSBapMeEk148M7cYmBygJDAaBpw==", + "appium-remote-debugger": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/appium-remote-debugger/-/appium-remote-debugger-4.5.0.tgz", + "integrity": "sha512-8UhFOQJvSCHSuWloGe+FOeIObIp6wzc5oaXV8vVRANn8rqZlQl2j4dgelQcA1DoE54Lphy00Bo99M7qDsKkF7w==", "dev": true, "requires": { "@babel/runtime": "^7.0.0", - "appium-support": "^2.48.0", - "async-lock": "^1.0.0", - "asyncbox": "^2.3.1", - "axios": "^0.20.0", - "bluebird": "^3.5.3", - "body-parser": "^1.18.2", - "colors": "^1.1.2", + "appium-base-driver": "^4.0.0", + "appium-support": "^2.28.0", + "asyncbox": "^2.5.2", + "bluebird": "^3.4.7", + "bufferpack": "0.0.6", "es6-error": "^4.1.1", - "express": "^4.16.2", - "http-status-codes": "^2.1.1", - "lodash": "^4.0.0", - "lru-cache": "^6.0.0", - "method-override": "^3.0.0", - "morgan": "^1.9.0", - "serve-favicon": "^2.4.5", + "lodash": "^4.17.11", + "request": "^2.79.0", + "request-promise": "^4.1.1", "source-map-support": "^0.5.5", - "validate.js": "^0.13.0", - "webdriverio": "^6.0.2", "ws": "^7.0.0" - } - }, - "appium-support": { - "version": "2.49.0", - "resolved": "https://registry.npmjs.org/appium-support/-/appium-support-2.49.0.tgz", - "integrity": "sha512-PtUsfpCjdGqZTTuRyH5U9iOGq9SpEVHg0H6/2HrazkX6ZvSQ+kIVYHTAGIXVeBJecfN9/syqgMmEekVgD15BGA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "archiver": "^5.0.0", - "axios": "^0.20.0", - "base64-stream": "^1.0.0", - "bluebird": "^3.5.1", - "bplist-creator": "^0", - "bplist-parser": "^0.2", - "form-data": "^3.0.0", - "get-stream": "^6.0.0", - "glob": "^7.1.2", - "jimp": "^0.14.0", - "jsftp": "^2.1.2", - "klaw": "^3.0.0", - "lockfile": "^1.0.4", - "lodash": "^4.2.1", - "mjpeg-server": "^0.3.0", - "mkdirp": "^1.0.0", - "moment": "^2.24.0", - "mv": "^2.1.1", - "ncp": "^2.0.0", - "npmlog": "^4.1.2", - "plist": "^3.0.1", - "pluralize": "^8.0.0", - "pngjs": "^5.0.0", - "rimraf": "^3.0.0", - "sanitize-filename": "^1.6.1", - "semver": "^7.0.0", - "shell-quote": "^1.7.2", - "source-map-support": "^0.5.5", - "teen_process": "^1.5.1", - "uuid": "^8.0.0", - "which": "^2.0.0", - "yauzl": "^2.7.0" }, "dependencies": { - "jimp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.14.0.tgz", - "integrity": "sha512-8BXU+J8+SPmwwyq9ELihpSV4dWPTiOKBWCEgtkbnxxAVMjXdf3yGmyaLSshBfXc8sP/JQ9OZj5R8nZzz2wPXgA==", + "appium-base-driver": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/appium-base-driver/-/appium-base-driver-4.5.1.tgz", + "integrity": "sha512-g7sI5mzmGdZhIFg3+A5f9ewtihYKS0b33wZWbN6R/PYYTEmXbNhFPGfVqzoAzFANuLjlTlvEEsqGcUtjrMO2Bw==", "dev": true, "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/custom": "^0.14.0", - "@jimp/plugins": "^0.14.0", - "@jimp/types": "^0.14.0", - "regenerator-runtime": "^0.13.3" + "@babel/runtime": "^7.0.0", + "appium-support": "^2.33.1", + "async-lock": "^1.0.0", + "asyncbox": "^2.3.1", + "bluebird": "^3.5.3", + "body-parser": "^1.18.2", + "colors": "^1.1.2", + "es6-error": "^4.1.1", + "express": "^4.16.2", + "http-status-codes": "^1.3.0", + "lodash": "^4.0.0", + "lru-cache": "^5.0.0", + "method-override": "^3.0.0", + "morgan": "^1.9.0", + "request": "^2.83.0", + "request-promise": "^4.2.2", + "sanitize-filename": "^1.6.1", + "serve-favicon": "^2.4.5", + "source-map-support": "^0.5.5", + "uuid-js": "^0.7.5", + "validate.js": "^0.13.0", + "webdriverio": "^5.10.9", + "ws": "^7.0.0" } - }, - "pngjs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", - "dev": true } } }, "archiver": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.0.2.tgz", - "integrity": "sha512-Tq3yV/T4wxBsD2Wign8W9VQKhaUxzzRmjEiSoOK0SLqPgDP/N1TKdYyBeIEu56T4I9iO4fKTTR0mN9NWkBA0sg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-3.1.1.tgz", + "integrity": "sha512-5Hxxcig7gw5Jod/8Gq0OneVgLYET+oNHcxgWItq4TbhOzRLKNAFUb9edAftiMKXvXfCB0vbGrJdZDNq0dWMsxg==", "dev": true, "requires": { "archiver-utils": "^2.1.0", - "async": "^3.2.0", + "async": "^2.6.3", "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", - "tar-stream": "^2.1.4", - "zip-stream": "^4.0.0" - }, - "dependencies": { - "async": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", - "dev": true - } + "glob": "^7.1.4", + "readable-stream": "^3.4.0", + "tar-stream": "^2.1.0", + "zip-stream": "^2.1.2" } }, - "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", "dev": true, "requires": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", + "lodash": "^4.17.14" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "compress-commons": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-2.1.1.tgz", + "integrity": "sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q==", + "dev": true, + "requires": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^3.0.1", "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" + "readable-stream": "^2.3.6" }, "dependencies": { "readable-stream": { @@ -23531,83 +15949,222 @@ } } }, - "axios": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", - "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", + "rgb2hex": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.10.tgz", + "integrity": "sha512-vKz+kzolWbL3rke/xeTE2+6vHmZnNxGyDnaVW4OckntAIcc7DcZzWkQSfxMDwqHS8vhgySnIFyBUH7lIk6PxvQ==", + "dev": true + }, + "serialize-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-5.0.0.tgz", + "integrity": "sha512-/VtpuyzYf82mHYTtI4QKtwHa79vAdU5OQpNPAmE/0UDdlGT0ZxHwC+J6gXkw29wwoVI8fMPsfcVHOwXtUQYYQA==", "dev": true, "requires": { - "follow-redirects": "^1.10.0" + "type-fest": "^0.8.0" } }, - "bl": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "ansi-regex": "^5.0.0" } }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "webdriver": { + "version": "5.23.0", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-5.23.0.tgz", + "integrity": "sha512-r7IrbZ2SuTIRyWV8mv4a4hZoFcT9Qt4MznOkdRWPE1sPpZ8lyLZsIEjKCEbHelOzPwURqk+biwGrm4z2OZRAiw==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@types/request": "^2.48.4", + "@wdio/config": "5.22.4", + "@wdio/logger": "5.16.10", + "@wdio/protocols": "5.22.1", + "@wdio/utils": "5.23.0", + "lodash.merge": "^4.6.1", + "request": "^2.83.0" } }, - "compress-commons": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.0.1.tgz", - "integrity": "sha512-xZm9o6iikekkI0GnXCmAl3LQGZj5TBDj0zLowsqi7tJtEa3FMGSEcHcqrSJIrOAk1UG/NBbDn/F1q+MG/p/EsA==", + "webdriverio": { + "version": "5.23.0", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-5.23.0.tgz", + "integrity": "sha512-hxt6Nuu2bBrTsVk7GfoFRTh63l4fRVXlK9U30RtPbHoWO5tcFdyUz2UTgeHEZ54ea1DQEVPfsgFiLnJULkWp1Q==", "dev": true, "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.0", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "@wdio/config": "5.22.4", + "@wdio/logger": "5.16.10", + "@wdio/repl": "5.23.0", + "@wdio/utils": "5.23.0", + "archiver": "^3.0.0", + "css-value": "^0.0.1", + "grapheme-splitter": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "lodash.isobject": "^3.0.2", + "lodash.isplainobject": "^4.0.6", + "lodash.zip": "^4.2.0", + "resq": "^1.6.0", + "rgb2hex": "^0.1.0", + "serialize-error": "^5.0.0", + "webdriver": "5.23.0" } }, - "crc32-stream": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.0.tgz", - "integrity": "sha512-tyMw2IeUX6t9jhgXI6um0eKfWq4EIDpfv5m7GX4Jzp7eVelQ360xd8EPXJhp2mHwLQIkqlnMLjzqSZI3a+0wRw==", + "xmldom": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.3.0.tgz", + "integrity": "sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g==", + "dev": true + }, + "xpath": { + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.24.tgz", + "integrity": "sha1-Gt4WLhzFI8jTn8fQavwW6iFvKfs=", + "dev": true + }, + "zip-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.3.tgz", + "integrity": "sha512-EkXc2JGcKhO5N5aZ7TmuNo45budRaFGHOmz24wtJR7znbNqDPmdZtUauKX6et8KAVseAMBOyWJqEpXcHTBsh7Q==", "dev": true, "requires": { - "crc": "^3.4.4", + "archiver-utils": "^2.1.0", + "compress-commons": "^2.1.1", "readable-stream": "^3.4.0" } - }, - "devtools": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-6.8.0.tgz", - "integrity": "sha512-DaXkQP312NclQPzSDHD3wvenglkxUDKiQwjlEqFvOXc+mc4o10WljVUx6B3Q0Ig7YmgBHsKREcNFYAwXTnzT7Q==", + } + } + }, + "appium-ios-simulator": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/appium-ios-simulator/-/appium-ios-simulator-3.22.1.tgz", + "integrity": "sha512-7DxAxY889AEY4Odfe64/v1pAM3BiJ8V89QqaHg/CvlZsP/Rp2RyLAEnMzR9SRjWeAeARbkhN1GCMQzThm3kVRg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.0.0", + "appium-support": "^2.44.0", + "appium-xcode": "^3.1.0", + "async-lock": "^1.0.0", + "asyncbox": "^2.3.1", + "bluebird": "^3.5.1", + "fkill": "^7.0.0", + "lodash": "^4.2.1", + "node-simctl": "^6.1.0", + "openssl-wrapper": "^0.3.4", + "semver": "^7.0.0", + "source-map-support": "^0.5.3", + "teen_process": "^1.3.0" + } + }, + "appium-mac-driver": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/appium-mac-driver/-/appium-mac-driver-1.10.0.tgz", + "integrity": "sha512-+isEfxtE7FQJr418udxfHBp9OU/S/tEjG8BbMgGjcHi8TE/4B78KzQZxgewGPRKraNLFo/58by8PqgWsS6sLtA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.0.0", + "appium-base-driver": "^5.0.0", + "appium-support": "^2.36.0", + "asyncbox": "^2.3.1", + "bluebird": "^3.5.1", + "lodash": "^4.17.4", + "source-map-support": "^0.5.5", + "teen_process": "^1.15.0", + "yargs": "^15.0.1" + }, + "dependencies": { + "appium-base-driver": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/appium-base-driver/-/appium-base-driver-5.8.1.tgz", + "integrity": "sha512-k5ybExgP0kJx7vsR0Y8GeEay+Vr0yCs0muzYtdrIDbqOCz5bFX0skyZubSSsm/lOE4KvgHhm4cRwWJM0DlHvRA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.0.0", + "appium-support": "^2.46.0", + "async-lock": "^1.0.0", + "asyncbox": "^2.3.1", + "axios": "^0.19.2", + "bluebird": "^3.5.3", + "body-parser": "^1.18.2", + "colors": "^1.1.2", + "es6-error": "^4.1.1", + "express": "^4.16.2", + "http-status-codes": "^1.3.0", + "lodash": "^4.0.0", + "lru-cache": "^5.0.0", + "method-override": "^3.0.0", + "morgan": "^1.9.0", + "request": "^2.88.2", + "request-promise": "^4.2.5", + "serve-favicon": "^2.4.5", + "source-map-support": "^0.5.5", + "validate.js": "^0.13.0", + "webdriverio": "^6.0.2", + "ws": "^7.0.0" + } + } + } + }, + "appium-remote-debugger": { + "version": "8.13.1", + "resolved": "https://registry.npmjs.org/appium-remote-debugger/-/appium-remote-debugger-8.13.1.tgz", + "integrity": "sha512-kVFDrRx84itAwiM0Cng2KR/rQca1PWGDIrzu7GUSixF4ARvGLl7K4L6st5NcSufjsJk87HojPitbCxkleajFlw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.0.0", + "appium-base-driver": "^7.0.0", + "appium-ios-device": "^1.7.0", + "appium-support": "^2.41.0", + "async-lock": "^1.2.2", + "asyncbox": "^2.6.0", + "bluebird": "^3.4.7", + "lodash": "^4.17.11", + "source-map-support": "^0.5.5" + }, + "dependencies": { + "appium-base-driver": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/appium-base-driver/-/appium-base-driver-7.3.0.tgz", + "integrity": "sha512-CVrpKPrch52KIDgXjMXfKs0/55R3X8TRkyu7xcun1cjl6nYlMb+wpoDCdlRmSBapMeEk148M7cYmBygJDAaBpw==", "dev": true, "requires": { - "@types/puppeteer-core": "^2.0.0", - "@types/ua-parser-js": "^0.7.33", - "@types/uuid": "^8.3.0", - "@wdio/config": "6.8.0", - "@wdio/logger": "6.8.0", - "@wdio/protocols": "6.8.0", - "@wdio/utils": "6.8.0", - "chrome-launcher": "^0.13.1", - "edge-paths": "^2.1.0", - "puppeteer-core": "^5.1.0", - "ua-parser-js": "^0.7.21", - "uuid": "^8.0.0" + "@babel/runtime": "^7.0.0", + "appium-support": "^2.48.0", + "async-lock": "^1.0.0", + "asyncbox": "^2.3.1", + "axios": "^0.20.0", + "bluebird": "^3.5.3", + "body-parser": "^1.18.2", + "colors": "^1.1.2", + "es6-error": "^4.1.1", + "express": "^4.16.2", + "http-status-codes": "^2.1.1", + "lodash": "^4.0.0", + "lru-cache": "^6.0.0", + "method-override": "^3.0.0", + "morgan": "^1.9.0", + "serve-favicon": "^2.4.5", + "source-map-support": "^0.5.5", + "validate.js": "^0.13.0", + "webdriverio": "^6.0.2", + "ws": "^7.0.0" } }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true + "axios": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", + "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", + "dev": true, + "requires": { + "follow-redirects": "^1.10.0" + } }, "follow-redirects": { "version": "1.13.0", @@ -23615,35 +16172,12 @@ "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", "dev": true }, - "form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", - "dev": true - }, "http-status-codes": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.1.4.tgz", "integrity": "sha512-MZVIsLKGVOVE1KEnldppe6Ij+vmemMuApDfjhVSLzyYP+td0bREEYyAoIw9yFePoBXManCuBqmiNP5FqJS5Xkg==", "dev": true }, - "jpeg-js": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.2.tgz", - "integrity": "sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw==", - "dev": true - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -23653,203 +16187,18 @@ "yallist": "^4.0.0" } }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "puppeteer-core": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-5.5.0.tgz", - "integrity": "sha512-tlA+1n+ziW/Db03hVV+bAecDKse8ihFRXYiEypBe9IlLRvOCzYFG6qrCMBYK34HO/Q/Ecjc+tvkHRAfLVH+NgQ==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.818844", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^4.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.0.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" - }, - "dependencies": { - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - } - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "resq": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/resq/-/resq-1.9.2.tgz", - "integrity": "sha512-Y+fprJ9wQY64gh+vJRNatiG61G+9XD5jJe4kI/Rqw6gmOa5ihZvgrxZVydqyM96xj75jwaRCPVYPU3RwsEk6ug==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1" - } - }, - "rgb2hex": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.0.tgz", - "integrity": "sha512-cHdNTwmTMPu/TpP1bJfdApd6MbD+Kzi4GNnM6h35mdFChhQPSi9cAI8J7DMn5kQDKX8NuBaQXAyo360Oa7tOEA==", - "dev": true - }, - "serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", - "dev": true, - "requires": { - "type-fest": "^0.13.1" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "tar-stream": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", - "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", - "dev": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true - }, - "uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", - "dev": true - }, - "webdriver": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-6.8.0.tgz", - "integrity": "sha512-KijngSC7ZjgdDoFuJO70StImWB3qiiLbvURiTNQ/oeQaP0CnerQX5qvYpgMnP1CZNhxyyUbb+1+w0zBqSNfWaA==", - "dev": true, - "requires": { - "@types/lodash.merge": "^4.6.6", - "@wdio/config": "6.8.0", - "@wdio/logger": "6.8.0", - "@wdio/protocols": "6.8.0", - "@wdio/utils": "6.8.0", - "got": "^11.0.2", - "lodash.merge": "^4.6.1" - } - }, - "webdriverio": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-6.8.0.tgz", - "integrity": "sha512-jVN73xLUcIF3PJYgeqzTbyak0QfqUegq/hjaIkLl8wQWRNubuEA3dN9L4U33AzOokYvcBXinemh3Ein35f1a8A==", - "dev": true, - "requires": { - "@types/archiver": "^3.1.1", - "@types/atob": "^2.1.2", - "@types/fs-extra": "^9.0.2", - "@types/lodash.clonedeep": "^4.5.6", - "@types/lodash.isplainobject": "^4.0.6", - "@types/puppeteer-core": "^2.0.0", - "@wdio/config": "6.8.0", - "@wdio/logger": "6.8.0", - "@wdio/repl": "6.8.0", - "@wdio/utils": "6.8.0", - "archiver": "^5.0.0", - "atob": "^2.1.2", - "css-value": "^0.0.1", - "devtools": "6.8.0", - "fs-extra": "^9.0.1", - "get-port": "^5.1.1", - "grapheme-splitter": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", - "lodash.zip": "^4.2.0", - "minimatch": "^3.0.4", - "puppeteer-core": "^5.1.0", - "resq": "^1.9.1", - "rgb2hex": "^0.2.0", - "serialize-error": "^7.0.0", - "webdriver": "6.8.0" - } - }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true - }, - "zip-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.0.2.tgz", - "integrity": "sha512-TGxB2g+1ur6MHkvM644DuZr8Uzyz0k0OYWtS3YlpfWBEmK4woaC2t3+pozEL3dBfIPmpgmClR5B2QRcMgGt22g==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.0.0", - "readable-stream": "^3.6.0" - } } } }, "appium-sdb": { - "version": "1.0.1-beta", - "resolved": "https://registry.npmjs.org/appium-sdb/-/appium-sdb-1.0.1-beta.tgz", - "integrity": "sha512-vTQv52JjlKLc/D/iJuJeFeZW0DYxSo9whF2DXu5UC3+Y1qUIHX91h2UBmNzs5NSbvIJ63lGWi7vMVnu6IhMdsQ==", + "version": "1.0.1-beta.1", + "resolved": "https://registry.npmjs.org/appium-sdb/-/appium-sdb-1.0.1-beta.1.tgz", + "integrity": "sha512-jciaEYrtZR7+NeB/KXrVLfQfvCQRdMpI4ZvrJWqyDOcUkzw5emH6nTGPTbkvnG9cf4opWM/e4wsgmHYnjgjByQ==", "dev": true, "requires": { "@babel/runtime": "^7.0.0", @@ -23861,23 +16210,26 @@ } }, "appium-support": { - "version": "2.39.1", - "resolved": "https://registry.npmjs.org/appium-support/-/appium-support-2.39.1.tgz", - "integrity": "sha512-FKDvP4pkt9N2KRzf5V+w8nNquoEIdNreOzM1OuSj4TxtG7S6Ix2LbXqjXjN4/nZKC5VEz1Vh+IQrSZ2uQv6MZg==", + "version": "2.48.1", + "resolved": "https://registry.npmjs.org/appium-support/-/appium-support-2.48.1.tgz", + "integrity": "sha512-vo6e8NdX5k+TFma1cc9uKy83mdyMAbZ0ugZRxyS7MwU11bDuepcd8dUktAI9MDWmiob+KjbOR/dG220pQ/M/xQ==", "dev": true, "requires": { "@babel/runtime": "^7.0.0", - "archiver": "^1.3.0", + "archiver": "^4.0.1", + "axios": "^0.19.2", + "base64-stream": "^1.0.0", "bluebird": "^3.5.1", "bplist-creator": "^0", "bplist-parser": "^0.2", - "extract-zip": "^1.6.0", + "form-data": "^3.0.0", + "get-stream": "^5.1.0", "glob": "^7.1.2", - "jimp": "^0.9.0", + "jimp": "^0.10.0", "jsftp": "^2.1.2", "klaw": "^3.0.0", + "lockfile": "^1.0.4", "lodash": "^4.2.1", - "md5-file": "^4.0.0", "mjpeg-server": "^0.3.0", "mkdirp": "^1.0.0", "moment": "^2.24.0", @@ -23886,26 +16238,27 @@ "npmlog": "^4.1.2", "plist": "^3.0.1", "pluralize": "^8.0.0", - "pngjs": "^3.0.0", - "request": "^2.83.0", - "request-promise": "^4.2.2", + "pngjs": "^5.0.0", "rimraf": "^3.0.0", + "sanitize-filename": "^1.6.1", "semver": "^7.0.0", + "shell-quote": "^1.7.2", "source-map-support": "^0.5.5", "teen_process": "^1.5.1", + "uuid": "^8.0.0", "which": "^2.0.0", "yauzl": "^2.7.0" } }, "appium-tizen-driver": { - "version": "1.1.1-beta.4", - "resolved": "https://registry.npmjs.org/appium-tizen-driver/-/appium-tizen-driver-1.1.1-beta.4.tgz", - "integrity": "sha512-lDOxwEAXUaoYwVMznFwYkHodceDahBNj2ImIGVDwSefEmEfR+1pZKvp1+SIeLRJfelsqVjnX8HQj8zRIP6lhKg==", + "version": "1.1.1-beta.5", + "resolved": "https://registry.npmjs.org/appium-tizen-driver/-/appium-tizen-driver-1.1.1-beta.5.tgz", + "integrity": "sha512-LKnGb84Fzcer8Bw772oR/WdKy1Eo2ILm8TPiU7ienFVdp0eDwmpFnfXl7vCzTnusohT4SDPnOOquMhkh4UMOfQ==", "dev": true, "requires": { "@babel/runtime": "^7.0.0", - "appium-base-driver": "^3.0.0", - "appium-sdb": "^1.0.1-beta", + "appium-base-driver": "^4.0.0", + "appium-sdb": "^1.0.1-beta.1", "appium-support": "^2.8.0", "asyncbox": "^2.0.4", "bluebird": "^3.4.7", @@ -24244,23 +16597,6 @@ "loglevel": "^1.6.0", "loglevel-plugin-prefix": "^0.8.4", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } } }, "@wdio/protocols": { @@ -24289,18 +16625,19 @@ } }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true }, "appium-base-driver": { - "version": "3.21.2", - "resolved": "https://registry.npmjs.org/appium-base-driver/-/appium-base-driver-3.21.2.tgz", - "integrity": "sha512-cA7adQK63OiZzCvuHfQ4jKcT3tFogo8dfbgbgS4xpcmIEXgUZd/6MHicQG4NdfzbJpojQXNSeeCkRvlpksQrOQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/appium-base-driver/-/appium-base-driver-4.5.1.tgz", + "integrity": "sha512-g7sI5mzmGdZhIFg3+A5f9ewtihYKS0b33wZWbN6R/PYYTEmXbNhFPGfVqzoAzFANuLjlTlvEEsqGcUtjrMO2Bw==", "dev": true, "requires": { "@babel/runtime": "^7.0.0", - "appium-support": "^2.24.0", + "appium-support": "^2.33.1", "async-lock": "^1.0.0", "asyncbox": "^2.3.1", "bluebird": "^3.5.3", @@ -24339,41 +16676,6 @@ "zip-stream": "^2.1.2" } }, - "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dev": true, - "requires": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } - } - }, "async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", @@ -24383,17 +16685,6 @@ "lodash": "^4.17.14" } }, - "bl": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -24467,16 +16758,6 @@ "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", "dev": true }, - "crc32-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-3.0.1.tgz", - "integrity": "sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==", - "dev": true, - "requires": { - "crc": "^3.4.4", - "readable-stream": "^3.4.0" - } - }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -24536,12 +16817,6 @@ "minimist": "0.0.8" } }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -24563,17 +16838,6 @@ "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", "dev": true }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", @@ -24623,24 +16887,12 @@ } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "tar-stream": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", - "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "ansi-regex": "^5.0.0" } }, "type-fest": { @@ -24734,525 +16986,98 @@ } } }, - "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - }, - "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "zip-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.3.tgz", - "integrity": "sha512-EkXc2JGcKhO5N5aZ7TmuNo45budRaFGHOmz24wtJR7znbNqDPmdZtUauKX6et8KAVseAMBOyWJqEpXcHTBsh7Q==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^2.1.1", - "readable-stream": "^3.4.0" - } - } - } - }, - "appium-uiautomator2-driver": { - "version": "1.44.2", - "resolved": "https://registry.npmjs.org/appium-uiautomator2-driver/-/appium-uiautomator2-driver-1.44.2.tgz", - "integrity": "sha512-xnfuQjXLjZP8XzUfTpnFk6nd6Qo5ixCLWK9wD10lPcmsYiqdHT8gSySaS5RglqYyCKC5mfQPwqKUMzSpCHKbig==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "appium-adb": "^7.20.0", - "appium-android-driver": "^4.25.0", - "appium-base-driver": "^5.0.0", - "appium-support": "^2.37.0", - "appium-uiautomator2-server": "^4.4.0", - "async-lock": "^1.2.2", - "asyncbox": "^2.3.1", - "bluebird": "^3.5.1", - "lodash": "^4.17.4", - "portscanner": "2.2.0", - "request": "^2.81.0", - "request-promise": "^4.1.1", - "source-map-support": "^0.5.5", - "teen_process": "^1.3.1", - "yargs": "^14.0.0" - } - }, - "appium-uiautomator2-server": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/appium-uiautomator2-server/-/appium-uiautomator2-server-4.5.5.tgz", - "integrity": "sha512-VtgdvOoC7jElOV5b+j0A3pB8c8D3Kll7b0EW2UR0+3Qs3gi0Xkzgouj/rtYv51elGeo/4NgmQ0hrQGqsEwIcfw==", - "dev": true - }, - "appium-webdriveragent": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/appium-webdriveragent/-/appium-webdriveragent-2.14.2.tgz", - "integrity": "sha512-C5/0Gh7rRPuvzYIqa2eVZX5RNP8wUzc4JdUhADS034rRCQQuShJghf3ZRSepottDGmPR3vMSau5sh0umtLWcaA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "appium-base-driver": "^5.0.2", - "appium-ios-simulator": "^3.14.0", - "appium-support": "^2.37.0", - "async-lock": "^1.0.0", - "asyncbox": "^2.5.3", - "bluebird": "^3.5.5", - "lodash": "^4.17.11", - "node-simctl": "^6.0.2", - "request": "^2.79.0", - "request-promise": "^4.1.1", - "source-map-support": "^0.5.12", - "stream-equal": "^1.1.1", - "teen_process": "^1.14.1" - }, - "dependencies": { - "@jimp/bmp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.14.0.tgz", - "integrity": "sha512-5RkX6tSS7K3K3xNEb2ygPuvyL9whjanhoaB/WmmXlJS6ub4DjTqrapu8j4qnIWmO4YYtFeTbDTXV6v9P1yMA5A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "bmp-js": "^0.1.0" - } - }, - "@jimp/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.14.0.tgz", - "integrity": "sha512-S62FcKdtLtj3yWsGfJRdFXSutjvHg7aQNiFogMbwq19RP4XJWqS2nOphu7ScB8KrSlyy5nPF2hkWNhLRLyD82w==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "any-base": "^1.1.0", - "buffer": "^5.2.0", - "exif-parser": "^0.1.12", - "file-type": "^9.0.0", - "load-bmfont": "^1.3.1", - "mkdirp": "^0.5.1", - "phin": "^2.9.1", - "pixelmatch": "^4.0.2", - "tinycolor2": "^1.4.1" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "@jimp/custom": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.14.0.tgz", - "integrity": "sha512-kQJMeH87+kWJdVw8F9GQhtsageqqxrvzg7yyOw3Tx/s7v5RToe8RnKyMM+kVtBJtNAG+Xyv/z01uYQ2jiZ3GwA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/core": "^0.14.0" - } - }, - "@jimp/gif": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.14.0.tgz", - "integrity": "sha512-DHjoOSfCaCz72+oGGEh8qH0zE6pUBaBxPxxmpYJjkNyDZP7RkbBkZJScIYeQ7BmJxmGN4/dZn+MxamoQlr+UYg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "gifwrap": "^0.9.2", - "omggif": "^1.0.9" - } - }, - "@jimp/jpeg": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.14.0.tgz", - "integrity": "sha512-561neGbr+87S/YVQYnZSTyjWTHBm9F6F1obYHiyU3wVmF+1CLbxY3FQzt4YolwyQHIBv36Bo0PY2KkkU8BEeeQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "jpeg-js": "^0.4.0" - } - }, - "@jimp/plugin-blit": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.14.0.tgz", - "integrity": "sha512-YoYOrnVHeX3InfgbJawAU601iTZMwEBZkyqcP1V/S33Qnz9uzH1Uj1NtC6fNgWzvX6I4XbCWwtr4RrGFb5CFrw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-blur": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.14.0.tgz", - "integrity": "sha512-9WhZcofLrT0hgI7t0chf7iBQZib//0gJh9WcQMUt5+Q1Bk04dWs8vTgLNj61GBqZXgHSPzE4OpCrrLDBG8zlhQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-circle": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.14.0.tgz", - "integrity": "sha512-o5L+wf6QA44tvTum5HeLyLSc5eVfIUd5ZDVi5iRfO4o6GT/zux9AxuTSkKwnjhsG8bn1dDmywAOQGAx7BjrQVA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-color": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.14.0.tgz", - "integrity": "sha512-JJz512SAILYV0M5LzBb9sbOm/XEj2fGElMiHAxb7aLI6jx+n0agxtHpfpV/AePTLm1vzzDxx6AJxXbKv355hBQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "tinycolor2": "^1.4.1" - } - }, - "@jimp/plugin-contain": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.14.0.tgz", - "integrity": "sha512-RX2q233lGyaxiMY6kAgnm9ScmEkNSof0hdlaJAVDS1OgXphGAYAeSIAwzESZN4x3ORaWvkFefeVH9O9/698Evg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-cover": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.14.0.tgz", - "integrity": "sha512-0P/5XhzWES4uMdvbi3beUgfvhn4YuQ/ny8ijs5kkYIw6K8mHcl820HahuGpwWMx56DJLHRl1hFhJwo9CeTRJtQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-crop": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.14.0.tgz", - "integrity": "sha512-Ojtih+XIe6/XSGtpWtbAXBozhCdsDMmy+THUJAGu2x7ZgKrMS0JotN+vN2YC3nwDpYkM+yOJImQeptSfZb2Sug==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-displace": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.14.0.tgz", - "integrity": "sha512-c75uQUzMgrHa8vegkgUvgRL/PRvD7paFbFJvzW0Ugs8Wl+CDMGIPYQ3j7IVaQkIS+cAxv+NJ3TIRBQyBrfVEOg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-dither": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.14.0.tgz", - "integrity": "sha512-g8SJqFLyYexXQQsoh4dc1VP87TwyOgeTElBcxSXX2LaaMZezypmxQfLTzOFzZoK8m39NuaoH21Ou1Ftsq7LzVQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-fisheye": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.14.0.tgz", - "integrity": "sha512-BFfUZ64EikCaABhCA6mR3bsltWhPpS321jpeIQfJyrILdpFsZ/OccNwCgpW1XlbldDHIoNtXTDGn3E+vCE7vDg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-flip": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.14.0.tgz", - "integrity": "sha512-WtL1hj6ryqHhApih+9qZQYA6Ye8a4HAmdTzLbYdTMrrrSUgIzFdiZsD0WeDHpgS/+QMsWwF+NFmTZmxNWqKfXw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-gaussian": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.14.0.tgz", - "integrity": "sha512-uaLwQ0XAQoydDlF9tlfc7iD9drYPriFe+jgYnWm8fbw5cN+eOIcnneEX9XCOOzwgLPkNCxGox6Kxjn8zY6GxtQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-invert": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.14.0.tgz", - "integrity": "sha512-UaQW9X9vx8orQXYSjT5VcITkJPwDaHwrBbxxPoDG+F/Zgv4oV9fP+udDD6qmkgI9taU+44Fy+zm/J/gGcMWrdg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-mask": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.14.0.tgz", - "integrity": "sha512-tdiGM69OBaKtSPfYSQeflzFhEpoRZ+BvKfDEoivyTjauynbjpRiwB1CaiS8En1INTDwzLXTT0Be9SpI3LkJoEA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-normalize": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.14.0.tgz", - "integrity": "sha512-AfY8sqlsbbdVwFGcyIPy5JH/7fnBzlmuweb+Qtx2vn29okq6+HelLjw2b+VT2btgGUmWWHGEHd86oRGSoWGyEQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-print": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.14.0.tgz", - "integrity": "sha512-MwP3sH+VS5AhhSTXk7pui+tEJFsxnTKFY3TraFJb8WFbA2Vo2qsRCZseEGwpTLhENB7p/JSsLvWoSSbpmxhFAQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "load-bmfont": "^1.4.0" - } - }, - "@jimp/plugin-resize": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.14.0.tgz", - "integrity": "sha512-qFeMOyXE/Bk6QXN0GQo89+CB2dQcXqoxUcDb2Ah8wdYlKqpi53skABkgVy5pW3EpiprDnzNDboMltdvDslNgLQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-rotate": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.14.0.tgz", - "integrity": "sha512-aGaicts44bvpTcq5Dtf93/8TZFu5pMo/61lWWnYmwJJU1RqtQlxbCLEQpMyRhKDNSfPbuP8nyGmaqXlM/82J0Q==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-scale": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.14.0.tgz", - "integrity": "sha512-ZcJk0hxY5ZKZDDwflqQNHEGRblgaR+piePZm7dPwPUOSeYEH31P0AwZ1ziceR74zd8N80M0TMft+e3Td6KGBHw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-shadow": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.14.0.tgz", - "integrity": "sha512-p2igcEr/iGrLiTu0YePNHyby0WYAXM14c5cECZIVnq/UTOOIQ7xIcWZJ1lRbAEPxVVXPN1UibhZAbr3HAb5BjQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-threshold": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.14.0.tgz", - "integrity": "sha512-N4BlDgm/FoOMV/DQM2rSpzsgqAzkP0DXkWZoqaQrlRxQBo4zizQLzhEL00T/YCCMKnddzgEhnByaocgaaa0fKw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugins": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.14.0.tgz", - "integrity": "sha512-vDO3XT/YQlFlFLq5TqNjQkISqjBHT8VMhpWhAfJVwuXIpilxz5Glu4IDLK6jp4IjPR6Yg2WO8TmRY/HI8vLrOw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/plugin-blit": "^0.14.0", - "@jimp/plugin-blur": "^0.14.0", - "@jimp/plugin-circle": "^0.14.0", - "@jimp/plugin-color": "^0.14.0", - "@jimp/plugin-contain": "^0.14.0", - "@jimp/plugin-cover": "^0.14.0", - "@jimp/plugin-crop": "^0.14.0", - "@jimp/plugin-displace": "^0.14.0", - "@jimp/plugin-dither": "^0.14.0", - "@jimp/plugin-fisheye": "^0.14.0", - "@jimp/plugin-flip": "^0.14.0", - "@jimp/plugin-gaussian": "^0.14.0", - "@jimp/plugin-invert": "^0.14.0", - "@jimp/plugin-mask": "^0.14.0", - "@jimp/plugin-normalize": "^0.14.0", - "@jimp/plugin-print": "^0.14.0", - "@jimp/plugin-resize": "^0.14.0", - "@jimp/plugin-rotate": "^0.14.0", - "@jimp/plugin-scale": "^0.14.0", - "@jimp/plugin-shadow": "^0.14.0", - "@jimp/plugin-threshold": "^0.14.0", - "timm": "^1.6.1" - } - }, - "@jimp/png": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.14.0.tgz", - "integrity": "sha512-0RV/mEIDOrPCcNfXSPmPBqqSZYwGADNRVUTyMt47RuZh7sugbYdv/uvKmQSiqRdR0L1sfbCBMWUEa5G/8MSbdA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "pngjs": "^3.3.3" - }, - "dependencies": { - "pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", - "dev": true - } - } - }, - "@jimp/tiff": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.14.0.tgz", - "integrity": "sha512-zBYDTlutc7j88G/7FBCn3kmQwWr0rmm1e0FKB4C3uJ5oYfT8645lftUsvosKVUEfkdmOaMAnhrf4ekaHcb5gQw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "utif": "^2.0.1" - } - }, - "@jimp/types": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.14.0.tgz", - "integrity": "sha512-hx3cXAW1KZm+b+XCrY3LXtdWy2U+hNtq0rPyJ7NuXCjU7lZR3vIkpz1DLJ3yDdS70hTi5QDXY3Cd9kd6DtloHQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/bmp": "^0.14.0", - "@jimp/gif": "^0.14.0", - "@jimp/jpeg": "^0.14.0", - "@jimp/png": "^0.14.0", - "@jimp/tiff": "^0.14.0", - "timm": "^1.6.1" - } - }, - "@jimp/utils": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.14.0.tgz", - "integrity": "sha512-MY5KFYUru0y74IsgM/9asDwb3ERxWxXEu3CRCZEvE7DtT86y1bR1XgtlSliMrptjz4qbivNGMQSvUBpEFJDp1A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "regenerator-runtime": "^0.13.3" - } - }, - "@wdio/config": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-6.8.0.tgz", - "integrity": "sha512-q71VJpa50iz9Bq0QoZIT+egZIAQGiv6mRcmqub8/R318XL35Y6NKHJIOyHl8xVmHwitYy//fawZou2nYjU66KQ==", - "dev": true, - "requires": { - "@wdio/logger": "6.8.0", - "deepmerge": "^4.0.0", - "glob": "^7.1.2" - } - }, - "@wdio/logger": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-6.8.0.tgz", - "integrity": "sha512-IvRnp2gTU1z6L+snMrKLrRDqYFq9yzcqXp7i6+Q/bxewxkgcpitm4hSs+13KS4fmbeBmhT5UeUeumnTZBYkhBQ==", + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "dev": true, "requires": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" } }, - "@wdio/protocols": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-6.8.0.tgz", - "integrity": "sha512-A9k3DYBxt220SK57LlALscHd/4KUa6kzJdc4UJ84Dyylymmjhs3Uau9WL37yyMMd6Y/5sSfUNRrAUEDZnmOzyQ==", - "dev": true - }, - "@wdio/repl": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-6.8.0.tgz", - "integrity": "sha512-unFnItXq6+V8JNfAtPtuEza047r2dLdcFXPN4exq7+O/kPJTzsTGOAQTlSLPJGMrfy5axTk90KOl08gpJvzjOA==", + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", "dev": true, "requires": { - "@wdio/utils": "6.8.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } }, - "@wdio/utils": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-6.8.0.tgz", - "integrity": "sha512-2vGwkaqP2e876o3NDTWz021aLTBrbZfCLHETuS+e/J0IXMR3FQ8et01BY/bjwyz6EP1I3vVtP2ZVC1dV2yIIVQ==", + "zip-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.3.tgz", + "integrity": "sha512-EkXc2JGcKhO5N5aZ7TmuNo45budRaFGHOmz24wtJR7znbNqDPmdZtUauKX6et8KAVseAMBOyWJqEpXcHTBsh7Q==", "dev": true, "requires": { - "@wdio/logger": "6.8.0" + "archiver-utils": "^2.1.0", + "compress-commons": "^2.1.1", + "readable-stream": "^3.4.0" } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, + } + } + }, + "appium-uiautomator2-driver": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/appium-uiautomator2-driver/-/appium-uiautomator2-driver-1.56.1.tgz", + "integrity": "sha512-rNYSMd1tZj2hLUD0NDGNkxMuiWn1LkyrNlPMxNIuTKNctk2SDiuSSuMXMGx5UJby7/AwpEQNn+vKB1xBKW2Blg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.0.0", + "appium-adb": "^8.5.0", + "appium-android-driver": "^4.37.0", + "appium-base-driver": "^6.2.0", + "appium-chromedriver": "^4.23.1", + "appium-support": "^2.44.0", + "appium-uiautomator2-server": "^4.8.0", + "asyncbox": "^2.3.1", + "axios": "^0.19.2", + "bluebird": "^3.5.1", + "lodash": "^4.17.4", + "portscanner": "2.2.0", + "source-map-support": "^0.5.5", + "teen_process": "^1.3.1", + "yargs": "^15.3.0" + } + }, + "appium-uiautomator2-server": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/appium-uiautomator2-server/-/appium-uiautomator2-server-4.12.2.tgz", + "integrity": "sha512-3LQhNFxUzTXrNY4hZoGDoaQAP9/l8XRwS1yySqSQpM5pNYQmVF68sNLgcJSQZXWJgHVzlxLIVuSmpbkZ3fZ5PA==", + "dev": true + }, + "appium-webdriveragent": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/appium-webdriveragent/-/appium-webdriveragent-2.22.2.tgz", + "integrity": "sha512-IMxy/ABh30eDhP3B6+lmNtHlpkbBmc7J+ugzftPRnb2XmXQt6cHVNjNjjCLUsCFzElt2jMX1/vpd78lllB9y/g==", + "dev": true, + "requires": { + "@babel/runtime": "^7.0.0", + "appium-base-driver": "^7.0.0", + "appium-ios-simulator": "^3.14.0", + "appium-support": "^2.46.0", + "async-lock": "^1.0.0", + "asyncbox": "^2.5.3", + "axios": "^0.19.2", + "bluebird": "^3.5.5", + "lodash": "^4.17.11", + "node-simctl": "^6.0.2", + "source-map-support": "^0.5.12", + "teen_process": "^1.14.1" + }, + "dependencies": { "appium-base-driver": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/appium-base-driver/-/appium-base-driver-7.3.0.tgz", @@ -25281,47 +17106,6 @@ "ws": "^7.0.0" }, "dependencies": { - "appium-support": { - "version": "2.49.0", - "resolved": "https://registry.npmjs.org/appium-support/-/appium-support-2.49.0.tgz", - "integrity": "sha512-PtUsfpCjdGqZTTuRyH5U9iOGq9SpEVHg0H6/2HrazkX6ZvSQ+kIVYHTAGIXVeBJecfN9/syqgMmEekVgD15BGA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "archiver": "^5.0.0", - "axios": "^0.20.0", - "base64-stream": "^1.0.0", - "bluebird": "^3.5.1", - "bplist-creator": "^0", - "bplist-parser": "^0.2", - "form-data": "^3.0.0", - "get-stream": "^6.0.0", - "glob": "^7.1.2", - "jimp": "^0.14.0", - "jsftp": "^2.1.2", - "klaw": "^3.0.0", - "lockfile": "^1.0.4", - "lodash": "^4.2.1", - "mjpeg-server": "^0.3.0", - "mkdirp": "^1.0.0", - "moment": "^2.24.0", - "mv": "^2.1.1", - "ncp": "^2.0.0", - "npmlog": "^4.1.2", - "plist": "^3.0.1", - "pluralize": "^8.0.0", - "pngjs": "^5.0.0", - "rimraf": "^3.0.0", - "sanitize-filename": "^1.6.1", - "semver": "^7.0.0", - "shell-quote": "^1.7.2", - "source-map-support": "^0.5.5", - "teen_process": "^1.5.1", - "uuid": "^8.0.0", - "which": "^2.0.0", - "yauzl": "^2.7.0" - } - }, "axios": { "version": "0.20.0", "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", @@ -25333,202 +17117,18 @@ } } }, - "archiver": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.0.2.tgz", - "integrity": "sha512-Tq3yV/T4wxBsD2Wign8W9VQKhaUxzzRmjEiSoOK0SLqPgDP/N1TKdYyBeIEu56T4I9iO4fKTTR0mN9NWkBA0sg==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "async": "^3.2.0", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", - "tar-stream": "^2.1.4", - "zip-stream": "^4.0.0" - } - }, - "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dev": true, - "requires": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } - } - }, - "async": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", - "dev": true - }, - "bl": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "compress-commons": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.0.1.tgz", - "integrity": "sha512-xZm9o6iikekkI0GnXCmAl3LQGZj5TBDj0zLowsqi7tJtEa3FMGSEcHcqrSJIrOAk1UG/NBbDn/F1q+MG/p/EsA==", - "dev": true, - "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.0", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - } - }, - "crc32-stream": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.0.tgz", - "integrity": "sha512-tyMw2IeUX6t9jhgXI6um0eKfWq4EIDpfv5m7GX4Jzp7eVelQ360xd8EPXJhp2mHwLQIkqlnMLjzqSZI3a+0wRw==", - "dev": true, - "requires": { - "crc": "^3.4.4", - "readable-stream": "^3.4.0" - } - }, - "devtools": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-6.8.0.tgz", - "integrity": "sha512-DaXkQP312NclQPzSDHD3wvenglkxUDKiQwjlEqFvOXc+mc4o10WljVUx6B3Q0Ig7YmgBHsKREcNFYAwXTnzT7Q==", - "dev": true, - "requires": { - "@types/puppeteer-core": "^2.0.0", - "@types/ua-parser-js": "^0.7.33", - "@types/uuid": "^8.3.0", - "@wdio/config": "6.8.0", - "@wdio/logger": "6.8.0", - "@wdio/protocols": "6.8.0", - "@wdio/utils": "6.8.0", - "chrome-launcher": "^0.13.1", - "edge-paths": "^2.1.0", - "puppeteer-core": "^5.1.0", - "ua-parser-js": "^0.7.21", - "uuid": "^8.0.0" - } - }, - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - } - } - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, "follow-redirects": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", "dev": true }, - "form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", - "dev": true - }, "http-status-codes": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.1.4.tgz", "integrity": "sha512-MZVIsLKGVOVE1KEnldppe6Ij+vmemMuApDfjhVSLzyYP+td0bREEYyAoIw9yFePoBXManCuBqmiNP5FqJS5Xkg==", "dev": true }, - "jimp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.14.0.tgz", - "integrity": "sha512-8BXU+J8+SPmwwyq9ELihpSV4dWPTiOKBWCEgtkbnxxAVMjXdf3yGmyaLSshBfXc8sP/JQ9OZj5R8nZzz2wPXgA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/custom": "^0.14.0", - "@jimp/plugins": "^0.14.0", - "@jimp/types": "^0.14.0", - "regenerator-runtime": "^0.13.3" - } - }, - "jpeg-js": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.2.tgz", - "integrity": "sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw==", - "dev": true - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -25538,195 +17138,26 @@ "yallist": "^4.0.0" } }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "pngjs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", - "dev": true - }, - "puppeteer-core": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-5.5.0.tgz", - "integrity": "sha512-tlA+1n+ziW/Db03hVV+bAecDKse8ihFRXYiEypBe9IlLRvOCzYFG6qrCMBYK34HO/Q/Ecjc+tvkHRAfLVH+NgQ==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.818844", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^4.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.0.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "resq": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/resq/-/resq-1.9.2.tgz", - "integrity": "sha512-Y+fprJ9wQY64gh+vJRNatiG61G+9XD5jJe4kI/Rqw6gmOa5ihZvgrxZVydqyM96xj75jwaRCPVYPU3RwsEk6ug==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1" - } - }, - "rgb2hex": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.0.tgz", - "integrity": "sha512-cHdNTwmTMPu/TpP1bJfdApd6MbD+Kzi4GNnM6h35mdFChhQPSi9cAI8J7DMn5kQDKX8NuBaQXAyo360Oa7tOEA==", - "dev": true - }, - "serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", - "dev": true, - "requires": { - "type-fest": "^0.13.1" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "tar-stream": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", - "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", - "dev": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true - }, - "uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", - "dev": true - }, - "webdriver": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-6.8.0.tgz", - "integrity": "sha512-KijngSC7ZjgdDoFuJO70StImWB3qiiLbvURiTNQ/oeQaP0CnerQX5qvYpgMnP1CZNhxyyUbb+1+w0zBqSNfWaA==", - "dev": true, - "requires": { - "@types/lodash.merge": "^4.6.6", - "@wdio/config": "6.8.0", - "@wdio/logger": "6.8.0", - "@wdio/protocols": "6.8.0", - "@wdio/utils": "6.8.0", - "got": "^11.0.2", - "lodash.merge": "^4.6.1" - } - }, - "webdriverio": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-6.8.0.tgz", - "integrity": "sha512-jVN73xLUcIF3PJYgeqzTbyak0QfqUegq/hjaIkLl8wQWRNubuEA3dN9L4U33AzOokYvcBXinemh3Ein35f1a8A==", - "dev": true, - "requires": { - "@types/archiver": "^3.1.1", - "@types/atob": "^2.1.2", - "@types/fs-extra": "^9.0.2", - "@types/lodash.clonedeep": "^4.5.6", - "@types/lodash.isplainobject": "^4.0.6", - "@types/puppeteer-core": "^2.0.0", - "@wdio/config": "6.8.0", - "@wdio/logger": "6.8.0", - "@wdio/repl": "6.8.0", - "@wdio/utils": "6.8.0", - "archiver": "^5.0.0", - "atob": "^2.1.2", - "css-value": "^0.0.1", - "devtools": "6.8.0", - "fs-extra": "^9.0.1", - "get-port": "^5.1.1", - "grapheme-splitter": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", - "lodash.zip": "^4.2.0", - "minimatch": "^3.0.4", - "puppeteer-core": "^5.1.0", - "resq": "^1.9.1", - "rgb2hex": "^0.2.0", - "serialize-error": "^7.0.0", - "webdriver": "6.8.0" - } - }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true - }, - "zip-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.0.2.tgz", - "integrity": "sha512-TGxB2g+1ur6MHkvM644DuZr8Uzyz0k0OYWtS3YlpfWBEmK4woaC2t3+pozEL3dBfIPmpgmClR5B2QRcMgGt22g==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.0.0", - "readable-stream": "^3.6.0" - } } } }, "appium-windows-driver": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/appium-windows-driver/-/appium-windows-driver-1.8.0.tgz", - "integrity": "sha512-z1orUQK60Pi4he+5CJ9gj3WTUYXenyk39NEsLxiuqp9uxb3DikgZ399chRmYkqg/dIQuIUKRhZmFz+VtV2Gj1g==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/appium-windows-driver/-/appium-windows-driver-1.13.0.tgz", + "integrity": "sha512-Bm1HBUH7zcA5q50tCz/Rhi80x6LLxn7DfFYhpuBHqH/rXgoK/5N0Zti+RjAMxIPY/IZeszST6G9dcaG0hUozWg==", "dev": true, "requires": { "@babel/runtime": "^7.0.0", - "appium-base-driver": "^5.0.0", - "appium-support": "^2.5.0", + "appium-base-driver": "^6.0.0", + "appium-support": "^2.47.1", "asyncbox": "^2.3.1", "bluebird": "^3.5.1", "lodash": "^4.6.1", - "request-promise": "^4.2.2", "source-map-support": "^0.5.5", "teen_process": "^1.7.0", "yargs": "^15.0.1" @@ -25749,20 +17180,20 @@ } }, "appium-xcuitest-driver": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/appium-xcuitest-driver/-/appium-xcuitest-driver-3.22.0.tgz", - "integrity": "sha512-hhjRz8EwUBBu1V0k9CwRN5CmQwreckEjnx7DFDjX3XQd/jfCBdNp5yKxqhjYveivvhiazPLTi3uoDy/aSs7Kpw==", + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/appium-xcuitest-driver/-/appium-xcuitest-driver-3.29.0.tgz", + "integrity": "sha512-G0lUtSW7PjANdbDNkB8h0229wG8PE9O/7otxLDETbhDGjKzS9XVCHTPwLMUMNTwMmYBbztnBGLAEBr2OSjPa6Q==", "dev": true, "requires": { "@babel/runtime": "^7.0.0", - "appium-base-driver": "^5.1.0", - "appium-idb": "^0", + "appium-base-driver": "^7.0.0", + "appium-idb": "^0.5.0", "appium-ios-device": "^1.5.0", - "appium-ios-driver": "^4.6.0", - "appium-ios-simulator": "^3.20.0", - "appium-remote-debugger": "^8.8.1", - "appium-support": "^2.41.0", - "appium-webdriveragent": "^2.14.1", + "appium-ios-driver": "^4.8.0", + "appium-ios-simulator": "^3.24.0", + "appium-remote-debugger": "^8.13.0", + "appium-support": "^2.47.1", + "appium-webdriveragent": "^2.22.2", "appium-xcode": "^3.8.0", "async-lock": "^1.0.0", "asyncbox": "^2.3.1", @@ -25773,379 +17204,14 @@ "moment-timezone": "0.5.28", "node-simctl": "^6.1.0", "portscanner": "2.2.0", - "request": "^2.79.0", - "request-promise": "^4.1.1", "semver": "^7.0.0", "source-map-support": "^0.5.5", "teen_process": "^1.14.0", "ws": "^7.0.0", "xmldom": "^0.3.0", - "yargs": "^15.0.1" + "yargs": "^16.0.3" }, "dependencies": { - "@jimp/bmp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.14.0.tgz", - "integrity": "sha512-5RkX6tSS7K3K3xNEb2ygPuvyL9whjanhoaB/WmmXlJS6ub4DjTqrapu8j4qnIWmO4YYtFeTbDTXV6v9P1yMA5A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "bmp-js": "^0.1.0" - } - }, - "@jimp/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.14.0.tgz", - "integrity": "sha512-S62FcKdtLtj3yWsGfJRdFXSutjvHg7aQNiFogMbwq19RP4XJWqS2nOphu7ScB8KrSlyy5nPF2hkWNhLRLyD82w==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "any-base": "^1.1.0", - "buffer": "^5.2.0", - "exif-parser": "^0.1.12", - "file-type": "^9.0.0", - "load-bmfont": "^1.3.1", - "mkdirp": "^0.5.1", - "phin": "^2.9.1", - "pixelmatch": "^4.0.2", - "tinycolor2": "^1.4.1" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "@jimp/custom": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.14.0.tgz", - "integrity": "sha512-kQJMeH87+kWJdVw8F9GQhtsageqqxrvzg7yyOw3Tx/s7v5RToe8RnKyMM+kVtBJtNAG+Xyv/z01uYQ2jiZ3GwA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/core": "^0.14.0" - } - }, - "@jimp/gif": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.14.0.tgz", - "integrity": "sha512-DHjoOSfCaCz72+oGGEh8qH0zE6pUBaBxPxxmpYJjkNyDZP7RkbBkZJScIYeQ7BmJxmGN4/dZn+MxamoQlr+UYg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "gifwrap": "^0.9.2", - "omggif": "^1.0.9" - } - }, - "@jimp/jpeg": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.14.0.tgz", - "integrity": "sha512-561neGbr+87S/YVQYnZSTyjWTHBm9F6F1obYHiyU3wVmF+1CLbxY3FQzt4YolwyQHIBv36Bo0PY2KkkU8BEeeQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "jpeg-js": "^0.4.0" - } - }, - "@jimp/plugin-blit": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.14.0.tgz", - "integrity": "sha512-YoYOrnVHeX3InfgbJawAU601iTZMwEBZkyqcP1V/S33Qnz9uzH1Uj1NtC6fNgWzvX6I4XbCWwtr4RrGFb5CFrw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-blur": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.14.0.tgz", - "integrity": "sha512-9WhZcofLrT0hgI7t0chf7iBQZib//0gJh9WcQMUt5+Q1Bk04dWs8vTgLNj61GBqZXgHSPzE4OpCrrLDBG8zlhQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-circle": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.14.0.tgz", - "integrity": "sha512-o5L+wf6QA44tvTum5HeLyLSc5eVfIUd5ZDVi5iRfO4o6GT/zux9AxuTSkKwnjhsG8bn1dDmywAOQGAx7BjrQVA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-color": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.14.0.tgz", - "integrity": "sha512-JJz512SAILYV0M5LzBb9sbOm/XEj2fGElMiHAxb7aLI6jx+n0agxtHpfpV/AePTLm1vzzDxx6AJxXbKv355hBQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "tinycolor2": "^1.4.1" - } - }, - "@jimp/plugin-contain": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.14.0.tgz", - "integrity": "sha512-RX2q233lGyaxiMY6kAgnm9ScmEkNSof0hdlaJAVDS1OgXphGAYAeSIAwzESZN4x3ORaWvkFefeVH9O9/698Evg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-cover": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.14.0.tgz", - "integrity": "sha512-0P/5XhzWES4uMdvbi3beUgfvhn4YuQ/ny8ijs5kkYIw6K8mHcl820HahuGpwWMx56DJLHRl1hFhJwo9CeTRJtQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-crop": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.14.0.tgz", - "integrity": "sha512-Ojtih+XIe6/XSGtpWtbAXBozhCdsDMmy+THUJAGu2x7ZgKrMS0JotN+vN2YC3nwDpYkM+yOJImQeptSfZb2Sug==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-displace": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.14.0.tgz", - "integrity": "sha512-c75uQUzMgrHa8vegkgUvgRL/PRvD7paFbFJvzW0Ugs8Wl+CDMGIPYQ3j7IVaQkIS+cAxv+NJ3TIRBQyBrfVEOg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-dither": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.14.0.tgz", - "integrity": "sha512-g8SJqFLyYexXQQsoh4dc1VP87TwyOgeTElBcxSXX2LaaMZezypmxQfLTzOFzZoK8m39NuaoH21Ou1Ftsq7LzVQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-fisheye": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.14.0.tgz", - "integrity": "sha512-BFfUZ64EikCaABhCA6mR3bsltWhPpS321jpeIQfJyrILdpFsZ/OccNwCgpW1XlbldDHIoNtXTDGn3E+vCE7vDg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-flip": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.14.0.tgz", - "integrity": "sha512-WtL1hj6ryqHhApih+9qZQYA6Ye8a4HAmdTzLbYdTMrrrSUgIzFdiZsD0WeDHpgS/+QMsWwF+NFmTZmxNWqKfXw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-gaussian": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.14.0.tgz", - "integrity": "sha512-uaLwQ0XAQoydDlF9tlfc7iD9drYPriFe+jgYnWm8fbw5cN+eOIcnneEX9XCOOzwgLPkNCxGox6Kxjn8zY6GxtQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-invert": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.14.0.tgz", - "integrity": "sha512-UaQW9X9vx8orQXYSjT5VcITkJPwDaHwrBbxxPoDG+F/Zgv4oV9fP+udDD6qmkgI9taU+44Fy+zm/J/gGcMWrdg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-mask": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.14.0.tgz", - "integrity": "sha512-tdiGM69OBaKtSPfYSQeflzFhEpoRZ+BvKfDEoivyTjauynbjpRiwB1CaiS8En1INTDwzLXTT0Be9SpI3LkJoEA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-normalize": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.14.0.tgz", - "integrity": "sha512-AfY8sqlsbbdVwFGcyIPy5JH/7fnBzlmuweb+Qtx2vn29okq6+HelLjw2b+VT2btgGUmWWHGEHd86oRGSoWGyEQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-print": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.14.0.tgz", - "integrity": "sha512-MwP3sH+VS5AhhSTXk7pui+tEJFsxnTKFY3TraFJb8WFbA2Vo2qsRCZseEGwpTLhENB7p/JSsLvWoSSbpmxhFAQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "load-bmfont": "^1.4.0" - } - }, - "@jimp/plugin-resize": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.14.0.tgz", - "integrity": "sha512-qFeMOyXE/Bk6QXN0GQo89+CB2dQcXqoxUcDb2Ah8wdYlKqpi53skABkgVy5pW3EpiprDnzNDboMltdvDslNgLQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-rotate": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.14.0.tgz", - "integrity": "sha512-aGaicts44bvpTcq5Dtf93/8TZFu5pMo/61lWWnYmwJJU1RqtQlxbCLEQpMyRhKDNSfPbuP8nyGmaqXlM/82J0Q==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-scale": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.14.0.tgz", - "integrity": "sha512-ZcJk0hxY5ZKZDDwflqQNHEGRblgaR+piePZm7dPwPUOSeYEH31P0AwZ1ziceR74zd8N80M0TMft+e3Td6KGBHw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-shadow": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.14.0.tgz", - "integrity": "sha512-p2igcEr/iGrLiTu0YePNHyby0WYAXM14c5cECZIVnq/UTOOIQ7xIcWZJ1lRbAEPxVVXPN1UibhZAbr3HAb5BjQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugin-threshold": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.14.0.tgz", - "integrity": "sha512-N4BlDgm/FoOMV/DQM2rSpzsgqAzkP0DXkWZoqaQrlRxQBo4zizQLzhEL00T/YCCMKnddzgEhnByaocgaaa0fKw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0" - } - }, - "@jimp/plugins": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.14.0.tgz", - "integrity": "sha512-vDO3XT/YQlFlFLq5TqNjQkISqjBHT8VMhpWhAfJVwuXIpilxz5Glu4IDLK6jp4IjPR6Yg2WO8TmRY/HI8vLrOw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/plugin-blit": "^0.14.0", - "@jimp/plugin-blur": "^0.14.0", - "@jimp/plugin-circle": "^0.14.0", - "@jimp/plugin-color": "^0.14.0", - "@jimp/plugin-contain": "^0.14.0", - "@jimp/plugin-cover": "^0.14.0", - "@jimp/plugin-crop": "^0.14.0", - "@jimp/plugin-displace": "^0.14.0", - "@jimp/plugin-dither": "^0.14.0", - "@jimp/plugin-fisheye": "^0.14.0", - "@jimp/plugin-flip": "^0.14.0", - "@jimp/plugin-gaussian": "^0.14.0", - "@jimp/plugin-invert": "^0.14.0", - "@jimp/plugin-mask": "^0.14.0", - "@jimp/plugin-normalize": "^0.14.0", - "@jimp/plugin-print": "^0.14.0", - "@jimp/plugin-resize": "^0.14.0", - "@jimp/plugin-rotate": "^0.14.0", - "@jimp/plugin-scale": "^0.14.0", - "@jimp/plugin-shadow": "^0.14.0", - "@jimp/plugin-threshold": "^0.14.0", - "timm": "^1.6.1" - } - }, - "@jimp/png": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.14.0.tgz", - "integrity": "sha512-0RV/mEIDOrPCcNfXSPmPBqqSZYwGADNRVUTyMt47RuZh7sugbYdv/uvKmQSiqRdR0L1sfbCBMWUEa5G/8MSbdA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.14.0", - "pngjs": "^3.3.3" - } - }, - "@jimp/tiff": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.14.0.tgz", - "integrity": "sha512-zBYDTlutc7j88G/7FBCn3kmQwWr0rmm1e0FKB4C3uJ5oYfT8645lftUsvosKVUEfkdmOaMAnhrf4ekaHcb5gQw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "utif": "^2.0.1" - } - }, - "@jimp/types": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.14.0.tgz", - "integrity": "sha512-hx3cXAW1KZm+b+XCrY3LXtdWy2U+hNtq0rPyJ7NuXCjU7lZR3vIkpz1DLJ3yDdS70hTi5QDXY3Cd9kd6DtloHQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/bmp": "^0.14.0", - "@jimp/gif": "^0.14.0", - "@jimp/jpeg": "^0.14.0", - "@jimp/png": "^0.14.0", - "@jimp/tiff": "^0.14.0", - "timm": "^1.6.1" - } - }, - "@jimp/utils": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.14.0.tgz", - "integrity": "sha512-MY5KFYUru0y74IsgM/9asDwb3ERxWxXEu3CRCZEvE7DtT86y1bR1XgtlSliMrptjz4qbivNGMQSvUBpEFJDp1A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "regenerator-runtime": "^0.13.3" - } - }, "@wdio/config": { "version": "5.22.4", "resolved": "https://registry.npmjs.org/@wdio/config/-/config-5.22.4.tgz", @@ -26415,112 +17481,6 @@ } } }, - "appium-support": { - "version": "2.49.0", - "resolved": "https://registry.npmjs.org/appium-support/-/appium-support-2.49.0.tgz", - "integrity": "sha512-PtUsfpCjdGqZTTuRyH5U9iOGq9SpEVHg0H6/2HrazkX6ZvSQ+kIVYHTAGIXVeBJecfN9/syqgMmEekVgD15BGA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "archiver": "^5.0.0", - "axios": "^0.20.0", - "base64-stream": "^1.0.0", - "bluebird": "^3.5.1", - "bplist-creator": "^0", - "bplist-parser": "^0.2", - "form-data": "^3.0.0", - "get-stream": "^6.0.0", - "glob": "^7.1.2", - "jimp": "^0.14.0", - "jsftp": "^2.1.2", - "klaw": "^3.0.0", - "lockfile": "^1.0.4", - "lodash": "^4.2.1", - "mjpeg-server": "^0.3.0", - "mkdirp": "^1.0.0", - "moment": "^2.24.0", - "mv": "^2.1.1", - "ncp": "^2.0.0", - "npmlog": "^4.1.2", - "plist": "^3.0.1", - "pluralize": "^8.0.0", - "pngjs": "^5.0.0", - "rimraf": "^3.0.0", - "sanitize-filename": "^1.6.1", - "semver": "^7.0.0", - "shell-quote": "^1.7.2", - "source-map-support": "^0.5.5", - "teen_process": "^1.5.1", - "uuid": "^8.0.0", - "which": "^2.0.0", - "yauzl": "^2.7.0" - }, - "dependencies": { - "archiver": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.0.2.tgz", - "integrity": "sha512-Tq3yV/T4wxBsD2Wign8W9VQKhaUxzzRmjEiSoOK0SLqPgDP/N1TKdYyBeIEu56T4I9iO4fKTTR0mN9NWkBA0sg==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "async": "^3.2.0", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", - "tar-stream": "^2.1.4", - "zip-stream": "^4.0.0" - } - }, - "async": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", - "dev": true - }, - "compress-commons": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.0.1.tgz", - "integrity": "sha512-xZm9o6iikekkI0GnXCmAl3LQGZj5TBDj0zLowsqi7tJtEa3FMGSEcHcqrSJIrOAk1UG/NBbDn/F1q+MG/p/EsA==", - "dev": true, - "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.0", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - } - }, - "jimp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.14.0.tgz", - "integrity": "sha512-8BXU+J8+SPmwwyq9ELihpSV4dWPTiOKBWCEgtkbnxxAVMjXdf3yGmyaLSshBfXc8sP/JQ9OZj5R8nZzz2wPXgA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/custom": "^0.14.0", - "@jimp/plugins": "^0.14.0", - "@jimp/types": "^0.14.0", - "regenerator-runtime": "^0.13.3" - } - }, - "pngjs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", - "dev": true - }, - "zip-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.0.2.tgz", - "integrity": "sha512-TGxB2g+1ur6MHkvM644DuZr8Uzyz0k0OYWtS3YlpfWBEmK4woaC2t3+pozEL3dBfIPmpgmClR5B2QRcMgGt22g==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.0.0", - "readable-stream": "^3.6.0" - } - } - } - }, "archiver": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/archiver/-/archiver-3.1.1.tgz", @@ -26536,41 +17496,6 @@ "zip-stream": "^2.1.2" } }, - "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dev": true, - "requires": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } - } - }, "async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", @@ -26589,17 +17514,6 @@ "follow-redirects": "^1.10.0" } }, - "bl": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -26633,29 +17547,6 @@ "readable-stream": "^2.3.6" }, "dependencies": { - "crc32-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-3.0.1.tgz", - "integrity": "sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==", - "dev": true, - "requires": { - "crc": "^3.4.4", - "readable-stream": "^3.4.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -26673,121 +17564,18 @@ } } }, - "crc32-stream": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.0.tgz", - "integrity": "sha512-tyMw2IeUX6t9jhgXI6um0eKfWq4EIDpfv5m7GX4Jzp7eVelQ360xd8EPXJhp2mHwLQIkqlnMLjzqSZI3a+0wRw==", - "dev": true, - "requires": { - "crc": "^3.4.4", - "readable-stream": "^3.4.0" - } - }, - "devtools": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-6.8.0.tgz", - "integrity": "sha512-DaXkQP312NclQPzSDHD3wvenglkxUDKiQwjlEqFvOXc+mc4o10WljVUx6B3Q0Ig7YmgBHsKREcNFYAwXTnzT7Q==", - "dev": true, - "requires": { - "@types/puppeteer-core": "^2.0.0", - "@types/ua-parser-js": "^0.7.33", - "@types/uuid": "^8.3.0", - "@wdio/config": "6.8.0", - "@wdio/logger": "6.8.0", - "@wdio/protocols": "6.8.0", - "@wdio/utils": "6.8.0", - "chrome-launcher": "^0.13.1", - "edge-paths": "^2.1.0", - "puppeteer-core": "^5.1.0", - "ua-parser-js": "^0.7.21", - "uuid": "^8.0.0" - }, - "dependencies": { - "@wdio/config": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-6.8.0.tgz", - "integrity": "sha512-q71VJpa50iz9Bq0QoZIT+egZIAQGiv6mRcmqub8/R318XL35Y6NKHJIOyHl8xVmHwitYy//fawZou2nYjU66KQ==", - "dev": true, - "requires": { - "@wdio/logger": "6.8.0", - "deepmerge": "^4.0.0", - "glob": "^7.1.2" - } - }, - "@wdio/logger": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-6.8.0.tgz", - "integrity": "sha512-IvRnp2gTU1z6L+snMrKLrRDqYFq9yzcqXp7i6+Q/bxewxkgcpitm4hSs+13KS4fmbeBmhT5UeUeumnTZBYkhBQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - } - }, - "@wdio/protocols": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-6.8.0.tgz", - "integrity": "sha512-A9k3DYBxt220SK57LlALscHd/4KUa6kzJdc4UJ84Dyylymmjhs3Uau9WL37yyMMd6Y/5sSfUNRrAUEDZnmOzyQ==", - "dev": true - }, - "@wdio/utils": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-6.8.0.tgz", - "integrity": "sha512-2vGwkaqP2e876o3NDTWz021aLTBrbZfCLHETuS+e/J0IXMR3FQ8et01BY/bjwyz6EP1I3vVtP2ZVC1dV2yIIVQ==", - "dev": true, - "requires": { - "@wdio/logger": "6.8.0" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, "escalade": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz", "integrity": "sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==", "dev": true }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, "follow-redirects": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", "dev": true }, - "form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", - "dev": true - }, "http-status-codes": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.1.4.tgz", @@ -26800,12 +17588,6 @@ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, - "jpeg-js": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.2.tgz", - "integrity": "sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw==", - "dev": true - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -26815,12 +17597,6 @@ "yallist": "^4.0.0" } }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, "moment-timezone": { "version": "0.5.28", "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.28.tgz", @@ -26830,75 +17606,6 @@ "moment": ">= 2.9.0" } }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "puppeteer-core": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-5.5.0.tgz", - "integrity": "sha512-tlA+1n+ziW/Db03hVV+bAecDKse8ihFRXYiEypBe9IlLRvOCzYFG6qrCMBYK34HO/Q/Ecjc+tvkHRAfLVH+NgQ==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.818844", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^4.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.0.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" - }, - "dependencies": { - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - } - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "resq": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/resq/-/resq-1.9.2.tgz", - "integrity": "sha512-Y+fprJ9wQY64gh+vJRNatiG61G+9XD5jJe4kI/Rqw6gmOa5ihZvgrxZVydqyM96xj75jwaRCPVYPU3RwsEk6ug==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1" - } - }, "rgb2hex": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.10.tgz", @@ -26934,31 +17641,12 @@ "ansi-regex": "^5.0.0" } }, - "tar-stream": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", - "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", - "dev": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, - "uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", - "dev": true - }, "webdriver": { "version": "5.23.0", "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-5.23.0.tgz", @@ -26974,180 +17662,6 @@ "request": "^2.83.0" } }, - "webdriverio": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-6.8.0.tgz", - "integrity": "sha512-jVN73xLUcIF3PJYgeqzTbyak0QfqUegq/hjaIkLl8wQWRNubuEA3dN9L4U33AzOokYvcBXinemh3Ein35f1a8A==", - "dev": true, - "requires": { - "@types/archiver": "^3.1.1", - "@types/atob": "^2.1.2", - "@types/fs-extra": "^9.0.2", - "@types/lodash.clonedeep": "^4.5.6", - "@types/lodash.isplainobject": "^4.0.6", - "@types/puppeteer-core": "^2.0.0", - "@wdio/config": "6.8.0", - "@wdio/logger": "6.8.0", - "@wdio/repl": "6.8.0", - "@wdio/utils": "6.8.0", - "archiver": "^5.0.0", - "atob": "^2.1.2", - "css-value": "^0.0.1", - "devtools": "6.8.0", - "fs-extra": "^9.0.1", - "get-port": "^5.1.1", - "grapheme-splitter": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", - "lodash.zip": "^4.2.0", - "minimatch": "^3.0.4", - "puppeteer-core": "^5.1.0", - "resq": "^1.9.1", - "rgb2hex": "^0.2.0", - "serialize-error": "^7.0.0", - "webdriver": "6.8.0" - }, - "dependencies": { - "@wdio/config": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-6.8.0.tgz", - "integrity": "sha512-q71VJpa50iz9Bq0QoZIT+egZIAQGiv6mRcmqub8/R318XL35Y6NKHJIOyHl8xVmHwitYy//fawZou2nYjU66KQ==", - "dev": true, - "requires": { - "@wdio/logger": "6.8.0", - "deepmerge": "^4.0.0", - "glob": "^7.1.2" - } - }, - "@wdio/logger": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-6.8.0.tgz", - "integrity": "sha512-IvRnp2gTU1z6L+snMrKLrRDqYFq9yzcqXp7i6+Q/bxewxkgcpitm4hSs+13KS4fmbeBmhT5UeUeumnTZBYkhBQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - } - }, - "@wdio/protocols": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-6.8.0.tgz", - "integrity": "sha512-A9k3DYBxt220SK57LlALscHd/4KUa6kzJdc4UJ84Dyylymmjhs3Uau9WL37yyMMd6Y/5sSfUNRrAUEDZnmOzyQ==", - "dev": true - }, - "@wdio/repl": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-6.8.0.tgz", - "integrity": "sha512-unFnItXq6+V8JNfAtPtuEza047r2dLdcFXPN4exq7+O/kPJTzsTGOAQTlSLPJGMrfy5axTk90KOl08gpJvzjOA==", - "dev": true, - "requires": { - "@wdio/utils": "6.8.0" - } - }, - "@wdio/utils": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-6.8.0.tgz", - "integrity": "sha512-2vGwkaqP2e876o3NDTWz021aLTBrbZfCLHETuS+e/J0IXMR3FQ8et01BY/bjwyz6EP1I3vVtP2ZVC1dV2yIIVQ==", - "dev": true, - "requires": { - "@wdio/logger": "6.8.0" - } - }, - "archiver": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.0.2.tgz", - "integrity": "sha512-Tq3yV/T4wxBsD2Wign8W9VQKhaUxzzRmjEiSoOK0SLqPgDP/N1TKdYyBeIEu56T4I9iO4fKTTR0mN9NWkBA0sg==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "async": "^3.2.0", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", - "tar-stream": "^2.1.4", - "zip-stream": "^4.0.0" - } - }, - "async": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", - "dev": true - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "compress-commons": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.0.1.tgz", - "integrity": "sha512-xZm9o6iikekkI0GnXCmAl3LQGZj5TBDj0zLowsqi7tJtEa3FMGSEcHcqrSJIrOAk1UG/NBbDn/F1q+MG/p/EsA==", - "dev": true, - "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.0", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - } - }, - "rgb2hex": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.0.tgz", - "integrity": "sha512-cHdNTwmTMPu/TpP1bJfdApd6MbD+Kzi4GNnM6h35mdFChhQPSi9cAI8J7DMn5kQDKX8NuBaQXAyo360Oa7tOEA==", - "dev": true - }, - "serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", - "dev": true, - "requires": { - "type-fest": "^0.13.1" - } - }, - "type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true - }, - "webdriver": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-6.8.0.tgz", - "integrity": "sha512-KijngSC7ZjgdDoFuJO70StImWB3qiiLbvURiTNQ/oeQaP0CnerQX5qvYpgMnP1CZNhxyyUbb+1+w0zBqSNfWaA==", - "dev": true, - "requires": { - "@types/lodash.merge": "^4.6.6", - "@wdio/config": "6.8.0", - "@wdio/logger": "6.8.0", - "@wdio/protocols": "6.8.0", - "@wdio/utils": "6.8.0", - "got": "^11.0.2", - "lodash.merge": "^4.6.1" - } - }, - "zip-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.0.2.tgz", - "integrity": "sha512-TGxB2g+1ur6MHkvM644DuZr8Uzyz0k0OYWtS3YlpfWBEmK4woaC2t3+pozEL3dBfIPmpgmClR5B2QRcMgGt22g==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.0.0", - "readable-stream": "^3.6.0" - } - } - } - }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -27218,9 +17732,9 @@ } }, "appium-youiengine-driver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/appium-youiengine-driver/-/appium-youiengine-driver-1.2.3.tgz", - "integrity": "sha512-N0isiXzcGn1RCHjQPDDewrXHgBurweaGRD0pGIAU9aIt94YsXBs3V8VYtRasx1iYjD/wem8YCLpX8ZOT87MkGg==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/appium-youiengine-driver/-/appium-youiengine-driver-1.2.5.tgz", + "integrity": "sha512-NtmSvsBPkkf+nQCBu0xDlps51jGEfhN58mFZv8jpelsbnX/M6l6upTfx9lUcr36/DaMBqBL8/hhk55fZEDrLuA==", "dev": true, "requires": { "@babel/runtime": "^7.0.0", @@ -27341,41 +17855,6 @@ "zip-stream": "^2.1.2" } }, - "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dev": true, - "requires": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } - } - }, "async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", @@ -27385,17 +17864,6 @@ "lodash": "^4.17.14" } }, - "bl": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -27435,16 +17903,6 @@ } } }, - "crc32-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-3.0.1.tgz", - "integrity": "sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==", - "dev": true, - "requires": { - "crc": "^3.4.4", - "readable-stream": "^3.4.0" - } - }, "node-simctl": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/node-simctl/-/node-simctl-5.3.0.tgz", @@ -27461,23 +17919,6 @@ "teen_process": "^1.5.1" } }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, "rgb2hex": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.10.tgz", @@ -27502,19 +17943,6 @@ "ansi-regex": "^5.0.0" } }, - "tar-stream": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", - "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", - "dev": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -27579,33 +18007,35 @@ "dev": true }, "archiver": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-1.3.0.tgz", - "integrity": "sha1-TyGU1tj5nfP1MeaIHxTxXVX6ryI=", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-4.0.2.tgz", + "integrity": "sha512-B9IZjlGwaxF33UN4oPbfBkyA4V1SxNLeIhR1qY8sRXSsbdUkEHrrOvwlYFPx+8uQeCe9M+FG6KgO+imDmQ79CQ==", "dev": true, "requires": { - "archiver-utils": "^1.3.0", - "async": "^2.0.0", + "archiver-utils": "^2.1.0", + "async": "^3.2.0", "buffer-crc32": "^0.2.1", - "glob": "^7.0.0", - "lodash": "^4.8.0", - "readable-stream": "^2.0.0", - "tar-stream": "^1.5.0", - "walkdir": "^0.0.11", - "zip-stream": "^1.1.0" + "glob": "^7.1.6", + "readable-stream": "^3.6.0", + "tar-stream": "^2.1.2", + "zip-stream": "^3.0.1" } }, "archiver-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz", - "integrity": "sha1-5QtMCccL89aA4y/xt5lOn52JUXQ=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "dev": true, "requires": { - "glob": "^7.0.0", - "graceful-fs": "^4.1.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", - "lodash": "^4.8.0", - "normalize-path": "^2.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", "readable-stream": "^2.0.0" }, "dependencies": { @@ -27721,13 +18151,10 @@ "dev": true }, "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", + "dev": true }, "async-limiter": { "version": "1.0.1", @@ -27754,9 +18181,9 @@ } }, "async-lock": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.2.2.tgz", - "integrity": "sha512-uczz62z2fMWOFbyo6rG4NlV2SdxugJT6sZA2QcfB1XaSjEiOh8CuOb/TttyMnYQCda6nkWecJe465tGQDPJiKw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.2.4.tgz", + "integrity": "sha512-UBQJC2pbeyGutIfYmErGc9RaJYnpZ1FHaxuKwb0ahvGiiCkPUf3p67Io+YLPmmv3RHY+mF6JEtNW8FlHsraAaA==", "dev": true }, "asyncbox": { @@ -27775,12 +18202,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, "aws-sign2": { @@ -27790,9 +18212,9 @@ "dev": true }, "aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", + "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", "dev": true }, "axios": { @@ -27831,12 +18253,14 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "base64-js": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true }, "base64-stream": { "version": "1.0.0", @@ -27869,13 +18293,14 @@ "dev": true }, "bl": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", + "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", "dev": true, "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" } }, "bluebird": { @@ -27922,12 +18347,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true } } }, @@ -27953,15 +18372,17 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "buffer": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz", - "integrity": "sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "dev": true, "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" @@ -28031,9 +18452,9 @@ "dev": true }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -28049,12 +18470,14 @@ "chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true }, "chrome-launcher": { "version": "0.13.4", "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.4.tgz", "integrity": "sha512-nnzXiDbGKjDSK6t2I+35OAPBy5Pw/39bgkb/ZAFwMhwJbdYBp6aH+vW28ZgtjdU890Q7D+3wN/tB8N66q5Gi2A==", + "dev": true, "requires": { "@types/node": "*", "escape-string-regexp": "^1.0.5", @@ -28064,15 +18487,11 @@ "rimraf": "^3.0.2" }, "dependencies": { - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, "requires": { "minimist": "^1.2.5" } @@ -28161,6 +18580,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "dev": true, "requires": { "color-convert": "^1.9.1", "color-string": "^1.5.2" @@ -28170,6 +18590,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "requires": { "color-name": "1.1.3" } @@ -28177,7 +18598,8 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true } } }, @@ -28193,12 +18615,14 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "color-string": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "dev": true, "requires": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" @@ -28220,6 +18644,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "dev": true, "requires": { "color": "3.0.x", "text-hex": "1.0.x" @@ -28229,6 +18654,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -28246,15 +18672,15 @@ "dev": true }, "compress-commons": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz", - "integrity": "sha1-UkqfEJA/OoEzibAiXSfEi7dRiQ8=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-3.0.0.tgz", + "integrity": "sha512-FyDqr8TKX5/X0qo+aVfaZ+PVmNJHJeckFBlq8jZGSJOgnynhfifoyl24qaqdUdDIBe0EVTHByN6NAkqYvE/2Xg==", "dev": true, "requires": { - "buffer-crc32": "^0.2.1", - "crc32-stream": "^2.0.0", - "normalize-path": "^2.0.0", - "readable-stream": "^2.0.0" + "buffer-crc32": "^0.2.13", + "crc32-stream": "^3.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^2.3.7" }, "dependencies": { "readable-stream": { @@ -28277,7 +18703,8 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "console-control-strings": { "version": "1.1.0", @@ -28323,9 +18750,10 @@ "dev": true }, "core-js": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", - "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==" + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -28343,19 +18771,19 @@ } }, "crc32-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-2.0.0.tgz", - "integrity": "sha1-483TtN8xaN10494/u8t7KX/pCPQ=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-3.0.1.tgz", + "integrity": "sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==", "dev": true, "requires": { "crc": "^3.4.4", - "readable-stream": "^2.0.0" + "readable-stream": "^3.4.0" } }, "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -28394,6 +18822,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, "requires": { "ms": "^2.1.1" } @@ -28445,7 +18874,8 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true }, "delegates": { "version": "1.0.0", @@ -28469,16 +18899,22 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/devtools/-/devtools-6.2.0.tgz", "integrity": "sha512-PmDKnIlDdP5+b6VurEnrIiLdzpSuWeWfge8tXJWjvFPhoqvwzdw4j5YSFo/QRWusS37Mk/j/rNUUawYP0u/ZKg==", + "dev": true, "requires": { + "@wdio/config": "6.1.14", + "@wdio/logger": "6.0.16", + "@wdio/protocols": "6.1.25", + "@wdio/utils": "6.2.0", "chrome-launcher": "^0.13.1", "puppeteer-core": "^4.0.0", - "ua-parser-js": "^0.7.21" + "ua-parser-js": "^0.7.21", + "uuid": "^8.0.0" } }, "dom-walk": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", - "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", "dev": true }, "duplexer": { @@ -28519,12 +18955,10 @@ "dev": true }, "enabled": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", - "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", - "requires": { - "env-variable": "0.0.x" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "dev": true }, "encodeurl": { "version": "1.0.2", @@ -28536,6 +18970,7 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, "requires": { "once": "^1.4.0" } @@ -28556,9 +18991,9 @@ } }, "es6-promisify": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-6.0.2.tgz", - "integrity": "sha512-eO6vFm0JvqGzjWIQA6QVKjxpmELfhWbDUWHm1rPfIbn55mhKPiAa5xpLmQWJrNa629ZIeQ8ZvMAi13kvrjK6Mg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-6.1.1.tgz", + "integrity": "sha512-HBL8I3mIki5C1Cc9QjKUenHtnG0A5/xA8Q/AllRcfiwl2CZFXGK7ddBiCoRwAix4i2KxcQfjtIVcrVbB3vbmwg==", "dev": true }, "escape-html": { @@ -28570,7 +19005,8 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, "etag": { "version": "1.8.1", @@ -28585,9 +19021,9 @@ "dev": true }, "execa": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.0.tgz", - "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz", + "integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -28659,12 +19095,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true } } }, @@ -28675,26 +19105,15 @@ "dev": true }, "extract-zip": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", - "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, "requires": { - "concat-stream": "1.6.2", - "debug": "2.6.9", - "mkdirp": "0.5.1", - "yauzl": "2.4.1" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - } + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" } }, "extsprintf": { @@ -28716,9 +19135,10 @@ } }, "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true }, "fast-json-stable-stringify": { "version": "2.1.0", @@ -28742,9 +19162,9 @@ } }, "fecha": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", - "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", + "integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==", "dev": true }, "file-type": { @@ -28802,9 +19222,9 @@ } }, "fkill": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/fkill/-/fkill-7.0.0.tgz", - "integrity": "sha512-i61SqvPdfCxl1/VQulh9SXrC+4dudCtINzTHbKaEx3Jr0kD9SvxKDeXzej7Saurnj3al/jMJwQnsUc62VrBMHQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fkill/-/fkill-7.0.1.tgz", + "integrity": "sha512-rziuWzpWErC2aGQUuvGo9dcVBDeHowK9g75u4fnkTCAqPgvUVRMtlDW6KWsWonxY1tjF+p1mIys33yNvLRlAtw==", "dev": true, "requires": { "aggregate-error": "^3.0.0", @@ -28819,7 +19239,8 @@ "fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "dev": true }, "follow-redirects": { "version": "1.5.10", @@ -28854,12 +19275,13 @@ "dev": true }, "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "dev": true, "requires": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, @@ -28878,31 +19300,14 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^1.0.0" - } + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fsevents": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.2.1.tgz", - "integrity": "sha512-bTLYHSeC0UH/EFXS9KqWnXuOl/wHK5Z/d+ghd5AsFMYN7wIGkUCOJyzy88+wJKkZPGON8u4Z9f6U4FdgURE9qA==", - "dev": true, - "optional": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "ftp-response-parser": { "version": "1.0.1", @@ -28961,12 +19366,6 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, - "get-port": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", - "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", - "dev": true - }, "get-prototype-of": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/get-prototype-of/-/get-prototype-of-0.0.0.tgz", @@ -28995,6 +19394,7 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -29034,9 +19434,9 @@ } }, "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, "grapheme-splitter": { @@ -29131,6 +19531,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "dev": true, "requires": { "agent-base": "5", "debug": "4" @@ -29154,7 +19555,8 @@ "ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true }, "immediate": { "version": "3.0.6", @@ -29172,6 +19574,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -29180,12 +19583,13 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "interpret": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true }, "invert-kv": { @@ -29195,9 +19599,9 @@ "dev": true }, "io.appium.settings": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/io.appium.settings/-/io.appium.settings-3.1.0.tgz", - "integrity": "sha512-s3OqMAD1CMTvYLLTijH/u36DLR6neqPxL7q9cD7qzEYycj8E7UzmvYg2amLf//xPSeCNjGpPbuVlk+ug29jSnQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/io.appium.settings/-/io.appium.settings-3.2.0.tgz", + "integrity": "sha512-2WQ5wIVCyitBU5JePVK98uNRze1v9D5vV6/DtJCVZt7STgRjhKwqVlveZii1HZ0wvbk7VU82s+wogpXne4muZw==", "dev": true }, "ipaddr.js": { @@ -29209,7 +19613,8 @@ "is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "dev": true }, "is-buffer": { "version": "1.1.6", @@ -29232,7 +19637,8 @@ "is-docker": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", - "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==" + "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==", + "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", @@ -29244,9 +19650,9 @@ } }, "is-function": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", - "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", "dev": true }, "is-number-like": { @@ -29274,6 +19680,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, "requires": { "is-docker": "^2.0.0" } @@ -29297,15 +19704,15 @@ "dev": true }, "jimp": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.9.5.tgz", - "integrity": "sha512-gjrzz+lT4In7shmP4LV1o/dfL0btnh4W9F5jPCXA6Qw4uEAF8+8GDwAR69hbUQCZH7R5KoCtq81tpfzydoJtSQ==", + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.10.3.tgz", + "integrity": "sha512-meVWmDMtyUG5uYjFkmzu0zBgnCvvxwWNi27c4cg55vWNVC9ES4Lcwb+ogx+uBBQE3Q+dLKjXaLl0JVW+nUNwbQ==", "dev": true, "requires": { "@babel/runtime": "^7.7.2", - "@jimp/custom": "^0.9.5", - "@jimp/plugins": "^0.9.5", - "@jimp/types": "^0.9.5", + "@jimp/custom": "^0.10.3", + "@jimp/plugins": "^0.10.3", + "@jimp/types": "^0.10.3", "core-js": "^3.4.1", "regenerator-runtime": "^0.13.3" } @@ -29377,24 +19784,6 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - }, - "dependencies": { - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } - } - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -29408,9 +19797,9 @@ } }, "jszip": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.2.tgz", - "integrity": "sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz", + "integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==", "dev": true, "requires": { "lie": "~3.3.0", @@ -29464,12 +19853,10 @@ } }, "kuler": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", - "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", - "requires": { - "colornames": "^1.1.1" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "dev": true }, "lazystream": { "version": "1.0.0", @@ -29519,6 +19906,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.2.0.tgz", "integrity": "sha512-wzUvdIeJZhRsG6gpZfmSCfysaxNEr43i+QT+Hie94wvHDKFLi4n7C2GqZ4sTC+PH5b5iktmXJvU87rWvhP3lHw==", + "dev": true, "requires": { "debug": "^2.6.8", "marky": "^1.2.0" @@ -29528,6 +19916,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -29535,14 +19924,15 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, "load-bmfont": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.0.tgz", - "integrity": "sha512-kT63aTAlNhZARowaNYcY29Fn/QYkc52M3l6V1ifRcPewg2lvUZDAj7R6dXjOL9D0sict76op3T5+odumDSF81g==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", + "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==", "dev": true, "requires": { "buffer-equal": "0.0.1", @@ -29574,9 +19964,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", "dev": true }, "lodash.clonedeep": { @@ -29640,22 +20030,22 @@ "dev": true }, "logform": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", - "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", + "integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==", "dev": true, "requires": { "colors": "^1.2.1", "fast-safe-stringify": "^2.0.4", - "fecha": "^2.3.3", + "fecha": "^4.2.0", "ms": "^2.1.1", "triple-beam": "^1.3.0" } }, "loglevel": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", - "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", + "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==", "dev": true }, "loglevel-plugin-prefix": { @@ -29700,7 +20090,8 @@ "marky": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.1.tgz", - "integrity": "sha512-md9k+Gxa3qLH6sUKpeC2CNkJK/Ld+bEz5X96nYwloqphQE0CKCVEKco/6jxEZixinqNdz5RFi/KaCyfbMDMAXQ==" + "integrity": "sha512-md9k+Gxa3qLH6sUKpeC2CNkJK/Ld+bEz5X96nYwloqphQE0CKCVEKco/6jxEZixinqNdz5RFi/KaCyfbMDMAXQ==", + "dev": true }, "md5": { "version": "2.2.1", @@ -29784,16 +20175,18 @@ "dev": true }, "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "dev": true }, "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dev": true, "requires": { - "mime-db": "1.43.0" + "mime-db": "1.44.0" } }, "mimic-fn": { @@ -29821,20 +20214,22 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, "mitt": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mitt/-/mitt-2.0.1.tgz", - "integrity": "sha512-FhuJY+tYHLnPcBHQhbUFzscD5512HumCPE4URXZUgPi3IvOJi4Xva5IIgy3xX56GqCmw++MAm5UURG6kDBYTdg==" + "integrity": "sha512-FhuJY+tYHLnPcBHQhbUFzscD5512HumCPE4URXZUgPi3IvOJi4Xva5IIgy3xX56GqCmw++MAm5UURG6kDBYTdg==", + "dev": true }, "mjpeg-server": { "version": "0.3.0", @@ -29843,42 +20238,43 @@ "dev": true }, "mkdirp": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz", - "integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true }, "mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true }, "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", + "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==", "dev": true }, "moment-timezone": { - "version": "0.5.28", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.28.tgz", - "integrity": "sha512-TDJkZvAyKIVWg5EtVqRzU97w0Rb0YVbfpqyjgu6GwXCAohVRqwZjf4fOzDE6p1Ch98Sro/8hQQi65WDXW5STPw==", + "version": "0.5.31", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", + "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", "dev": true, "requires": { "moment": ">= 2.9.0" } }, "morgan": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", - "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", "dev": true, "requires": { - "basic-auth": "~2.0.0", + "basic-auth": "~2.0.1", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "~2.0.0", "on-finished": "~2.3.0", - "on-headers": "~1.0.1" + "on-headers": "~1.0.2" }, "dependencies": { "debug": { @@ -29907,7 +20303,8 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "mv": { "version": "2.1.1", @@ -29934,12 +20331,12 @@ } }, "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "dev": true, "requires": { - "minimist": "0.0.8" + "minimist": "^1.2.5" } }, "rimraf": { @@ -29994,13 +20391,10 @@ } }, "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, "normalize-url": { "version": "4.5.0", @@ -30072,15 +20466,19 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } }, "one-time": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", - "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dev": true, + "requires": { + "fn.name": "1.x.x" + } }, "onetime": { "version": "5.1.0", @@ -30229,9 +20627,9 @@ "dev": true }, "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -30313,7 +20711,8 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-key": { "version": "3.1.1", @@ -30484,15 +20883,6 @@ } } }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, "plist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.1.tgz", @@ -30519,15 +20909,15 @@ "dev": true }, "pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", "dev": true }, "portfinder": { - "version": "1.0.25", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", - "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz", + "integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==", "dev": true, "requires": { "async": "^2.6.2", @@ -30554,12 +20944,12 @@ } }, "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "dev": true, "requires": { - "minimist": "0.0.8" + "minimist": "^1.2.5" } } } @@ -30617,7 +21007,8 @@ "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true }, "proxy-addr": { "version": "2.0.6", @@ -30632,12 +21023,13 @@ "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true }, "ps-list": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ps-list/-/ps-list-7.0.0.tgz", - "integrity": "sha512-ZDhdxqb+kE895BAvqIdGnWwfvB43h7KHMIcJC0hw7xLbbiJoprS+bqZxuGZ0jWdDxZEvB3jpnfgJyOn3lmsH+Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ps-list/-/ps-list-7.2.0.tgz", + "integrity": "sha512-v4Bl6I3f2kJfr5o80ShABNHAokIgY+wFDTQfE+X3zWYgSGQOCBeYptLZUpoOALBqO5EawmDN/tjTldJesd0ujQ==", "dev": true }, "pseudomap": { @@ -30647,15 +21039,16 @@ "dev": true }, "psl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", - "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", "dev": true }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -30671,8 +21064,10 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-4.0.1.tgz", "integrity": "sha512-8OfHmUkEXU/k7Bmcdm6KRWhIIfmayv/ce1AUDkP0nTLK2IpjulFggxq1dZhWgWHXebeLILbieMvAor7tSf3EqQ==", + "dev": true, "requires": { "debug": "^4.1.0", + "extract-zip": "^2.0.0", "https-proxy-agent": "^4.0.0", "mime": "^2.0.3", "mitt": "^2.0.1", @@ -30687,14 +21082,16 @@ "mime": { "version": "2.4.6", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "dev": true } } }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true }, "quick-lru": { "version": "5.1.1", @@ -30721,18 +21118,14 @@ } }, "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, "rechoir": { @@ -30745,9 +21138,10 @@ } }, "regenerator-runtime": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz", - "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==" + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "dev": true }, "request": { "version": "2.88.2", @@ -30836,9 +21230,9 @@ "dev": true }, "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -30866,26 +21260,19 @@ "dev": true, "requires": { "fast-deep-equal": "^2.0.1" - }, - "dependencies": { - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - } } }, "rgb2hex": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.10.tgz", - "integrity": "sha512-vKz+kzolWbL3rke/xeTE2+6vHmZnNxGyDnaVW4OckntAIcc7DcZzWkQSfxMDwqHS8vhgySnIFyBUH7lIk6PxvQ==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.0.tgz", + "integrity": "sha512-cHdNTwmTMPu/TpP1bJfdApd6MbD+Kzi4GNnM6h35mdFChhQPSi9cAI8J7DMn5kQDKX8NuBaQXAyo360Oa7tOEA==", "dev": true }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, "requires": { "glob": "^7.1.3" } @@ -30931,7 +21318,8 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -30978,9 +21366,9 @@ } }, "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true }, "send": { @@ -31030,12 +21418,12 @@ } }, "serialize-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-5.0.0.tgz", - "integrity": "sha512-/VtpuyzYf82mHYTtI4QKtwHa79vAdU5OQpNPAmE/0UDdlGT0ZxHwC+J6gXkw29wwoVI8fMPsfcVHOwXtUQYYQA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", "dev": true, "requires": { - "type-fest": "^0.8.0" + "type-fest": "^0.13.1" } }, "serve-favicon": { @@ -31135,9 +21523,9 @@ "dev": true }, "shelljs": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", - "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", + "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", "dev": true, "requires": { "glob": "^7.0.0", @@ -31152,15 +21540,16 @@ "dev": true }, "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, "simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "dev": true, "requires": { "is-arrayish": "^0.3.1" } @@ -31172,9 +21561,9 @@ "dev": true }, "source-map-support": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", - "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -31253,6 +21642,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -31291,60 +21681,25 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz", "integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==", + "dev": true, "requires": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.0.0" - }, - "dependencies": { - "bl": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "tar-stream": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", - "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - } } }, "tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz", + "integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==", "dev": true, "requires": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", + "bl": "^4.0.1", + "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" } }, "taskkill": { @@ -31399,12 +21754,14 @@ "text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "dev": true }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true }, "time-stamp": { "version": "1.1.0", @@ -31486,9 +21843,9 @@ "dev": true }, "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", "dev": true }, "type-is": { @@ -31504,23 +21861,19 @@ "ua-parser-js": { "version": "0.7.21", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", - "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==" + "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==", + "dev": true }, "unbzip2-stream": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, "requires": { "buffer": "^5.2.1", "through": "^2.3.8" } }, - "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", - "dev": true - }, "unorm": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz", @@ -31577,7 +21930,8 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true }, "utils-merge": { "version": "1.0.1", @@ -31586,9 +21940,10 @@ "dev": true }, "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz", + "integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==", + "dev": true }, "uuid-js": { "version": "0.7.5", @@ -31620,183 +21975,41 @@ } }, "webdriver": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-5.19.0.tgz", - "integrity": "sha512-lPHhLqySiwEQr61R+/UD4KjImCxPEQTgipU5nIFUJFJxo97dIBolTHpXVcGazsrQc3nSPjiHY9CrKjuxl2MjCQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-6.2.0.tgz", + "integrity": "sha512-8jcuMK0ygfVT0ySImE89TD79rmYzvKS3ImG/+xvS9CUzrlYJpXTos8OTM962YaqkX/nEiGlT4HdV+4SH1J27WQ==", "dev": true, "requires": { - "@wdio/config": "5.18.4", - "@wdio/logger": "5.16.10", - "@wdio/protocols": "5.19.0", - "@wdio/utils": "5.18.6", - "lodash.merge": "^4.6.1", - "request": "^2.83.0" + "@wdio/config": "6.1.14", + "@wdio/logger": "6.0.16", + "@wdio/protocols": "6.1.25", + "@wdio/utils": "6.2.0", + "got": "^11.0.2", + "lodash.merge": "^4.6.1" } }, "webdriverio": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-5.19.0.tgz", - "integrity": "sha512-0HFKkE9RqfTGlJbZ2XyxpHDH+Seya5x9wHs8USL5PSrxxnEljcrVc6qya85o6zFlcTgGzC5WM6UDycXkfa1cLg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-6.2.0.tgz", + "integrity": "sha512-TpikNm7Glm9qqWfvVbO5u9BbmDpPULI1PsJG9eDgL8eP7QoDsJs3gYwAizoBZlcRB9u/m8PESN/+B5H14NCvkA==", "dev": true, "requires": { - "@wdio/config": "5.18.4", - "@wdio/logger": "5.16.10", - "@wdio/repl": "5.18.6", - "@wdio/utils": "5.18.6", - "archiver": "^3.0.0", + "@wdio/config": "6.1.14", + "@wdio/logger": "6.0.16", + "@wdio/repl": "6.2.0", + "@wdio/utils": "6.2.0", + "archiver": "^4.0.1", "css-value": "^0.0.1", + "devtools": "6.2.0", "grapheme-splitter": "^1.0.2", "lodash.clonedeep": "^4.5.0", "lodash.isobject": "^3.0.2", "lodash.isplainobject": "^4.0.6", "lodash.zip": "^4.2.0", "resq": "^1.6.0", - "rgb2hex": "^0.1.0", - "serialize-error": "^5.0.0", - "webdriver": "5.19.0" - }, - "dependencies": { - "archiver": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-3.1.1.tgz", - "integrity": "sha512-5Hxxcig7gw5Jod/8Gq0OneVgLYET+oNHcxgWItq4TbhOzRLKNAFUb9edAftiMKXvXfCB0vbGrJdZDNq0dWMsxg==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "async": "^2.6.3", - "buffer-crc32": "^0.2.1", - "glob": "^7.1.4", - "readable-stream": "^3.4.0", - "tar-stream": "^2.1.0", - "zip-stream": "^2.1.2" - } - }, - "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dev": true, - "requires": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } - } - }, - "bl": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "compress-commons": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-2.1.1.tgz", - "integrity": "sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q==", - "dev": true, - "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^3.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^2.3.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } - } - }, - "crc32-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-3.0.1.tgz", - "integrity": "sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==", - "dev": true, - "requires": { - "crc": "^3.4.4", - "readable-stream": "^3.4.0" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "tar-stream": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", - "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", - "dev": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "zip-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.3.tgz", - "integrity": "sha512-EkXc2JGcKhO5N5aZ7TmuNo45budRaFGHOmz24wtJR7znbNqDPmdZtUauKX6et8KAVseAMBOyWJqEpXcHTBsh7Q==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^2.1.1", - "readable-stream": "^3.4.0" - } - } + "rgb2hex": "^0.2.0", + "serialize-error": "^7.0.0", + "webdriver": "6.2.0" } }, "which": { @@ -31824,42 +22037,29 @@ } }, "winston": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", - "integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", + "integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==", "dev": true, "requires": { - "async": "^2.6.1", - "diagnostics": "^1.1.1", - "is-stream": "^1.1.0", - "logform": "^2.1.1", - "one-time": "0.0.4", - "readable-stream": "^3.1.1", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.1.0", + "is-stream": "^2.0.0", + "logform": "^2.2.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", - "winston-transport": "^4.3.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } + "winston-transport": "^4.4.0" } }, "winston-transport": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz", - "integrity": "sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz", + "integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==", "dev": true, "requires": { - "readable-stream": "^2.3.6", + "readable-stream": "^2.3.7", "triple-beam": "^1.2.0" }, "dependencies": { @@ -31934,12 +22134,14 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "ws": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", - "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==" + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", + "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", + "dev": true }, "xhr": { "version": "2.5.0", @@ -32006,9 +22208,9 @@ "dev": true }, "yargs": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.0.tgz", - "integrity": "sha512-g/QCnmjgOl1YJjGsnUg2SatC7NUYEiLXJqxNOQU9qSpjzGtGXda9b+OKccr1kLTy8BN9yqEyqfq5lxlwdc13TA==", + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, "requires": { "cliui": "^6.0.0", @@ -32021,7 +22223,7 @@ "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^18.1.0" + "yargs-parser": "^18.1.2" }, "dependencies": { "ansi-regex": { @@ -32059,9 +22261,9 @@ } }, "yargs-parser": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.0.tgz", - "integrity": "sha512-o/Jr6JBOv6Yx3pL+5naWSoIA2jJ+ZkMYQG/ie9qFbukBe4uzmBatlXFOiu/tNKRWEtyf+n5w7jc/O16ufqOTdQ==", + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -32079,15 +22281,14 @@ } }, "zip-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz", - "integrity": "sha1-qLxF9MG0lpnGuQGYuqyqzbzUugQ=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-3.0.1.tgz", + "integrity": "sha512-r+JdDipt93ttDjsOVPU5zaq5bAyY+3H19bDrThkvuVxC0xMQzU1PJcS6D+KrP3u96gH9XLomcHPb+2skoDjulQ==", "dev": true, "requires": { - "archiver-utils": "^1.3.0", - "compress-commons": "^1.2.0", - "lodash": "^4.8.0", - "readable-stream": "^2.0.0" + "archiver-utils": "^2.1.0", + "compress-commons": "^3.0.0", + "readable-stream": "^3.6.0" } } } @@ -32095,7 +22296,8 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true }, "archiver": { "version": "3.1.1", @@ -32240,6 +22442,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -32786,12 +22989,6 @@ "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", "dev": true }, - "async-foreach": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", - "dev": true - }, "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", @@ -32878,15 +23075,6 @@ "integrity": "sha512-HZpLE7xu05+8AbpqXITGdxp1Xwk8ysAXrg7MiKRY27py3DAyEJpoJQo1727pWF3F+O79V3r+cTWhOzfB49P89w==", "dev": true }, - "axe-puppeteer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/axe-puppeteer/-/axe-puppeteer-1.1.0.tgz", - "integrity": "sha512-VS17Y1rDQe6A0PdeTPxwOSBfmOdj6efgxyre9cN1du1snnVilczSDtQsgifBKBlzoL/3DKfGpgIi+N+zrzODOg==", - "dev": true, - "requires": { - "axe-core": "^3.5.3" - } - }, "axobject-query": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", @@ -33164,9 +23352,9 @@ } }, "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.2.0.tgz", + "integrity": "sha512-pKnaUh2TNvk+/egJdBw1h46LwyLx8BzEq+MGCf/RMCVfEHHsGOCWG00dqk91kUPPArIIwMBg9T/virxwzP03cA==", "dev": true, "optional": true }, @@ -33549,6 +23737,12 @@ "@mdx-js/util": "^1.5.8" }, "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==", + "dev": true + }, "@mdx-js/util": { "version": "1.5.8", "resolved": "https://registry.npmjs.org/@mdx-js/util/-/util-1.5.8.tgz", @@ -33557,14 +23751,6 @@ } } }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", - "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", - "requires": { - "object.assign": "^4.1.0" - } - }, "babel-plugin-emotion": { "version": "10.0.33", "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.33.tgz", @@ -33623,6 +23809,14 @@ "dev": true, "requires": { "@babel/helper-plugin-utils": "7.8.3" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==", + "dev": true + } } }, "babel-plugin-inline-json-import": { @@ -34427,23 +24621,23 @@ "integrity": "sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw==", "dev": true }, - "bl": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", - "dev": true, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" + "file-uri-to-path": "1.0.0" } }, - "block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", "dev": true, "requires": { - "inherits": "~2.0.0" + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" } }, "bluebird": { @@ -34849,33 +25043,46 @@ } }, "browserslist": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.0.tgz", - "integrity": "sha512-pUsXKAF2lVwhmtpeA3LJrZ76jXuusrNyhduuQs7CDFf9foT4Y38aQOserd2lMe5DSSrjf3fx34oHwryuvxAUgQ==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.15.0.tgz", + "integrity": "sha512-IJ1iysdMkGmjjYeRlDU8PQejVwxvVO5QOfXH7ylW31GO6LwNRSmm/SgRXtNsEXqMLl2e+2H5eEJ7sfynF8TCaQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001111", - "electron-to-chromium": "^1.3.523", - "escalade": "^3.0.2", - "node-releases": "^1.1.60" + "caniuse-lite": "^1.0.30001164", + "colorette": "^1.2.1", + "electron-to-chromium": "^1.3.612", + "escalade": "^3.1.1", + "node-releases": "^1.1.67" }, "dependencies": { "caniuse-lite": { - "version": "1.0.30001117", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001117.tgz", - "integrity": "sha512-4tY0Fatzdx59kYjQs+bNxUwZB03ZEBgVmJ1UkFPz/Q8OLiUUbjct2EdpnXj0fvFTPej2EkbPIG0w8BWsjAyk1Q==", + "version": "1.0.30001165", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001165.tgz", + "integrity": "sha512-8cEsSMwXfx7lWSUMA2s08z9dIgsnR5NAqjXP23stdsU3AUWkCr/rr4s4OFtHXn5XXr6+7kam3QFVoYyXNPdJPA==", + "dev": true + }, + "colorette": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", "dev": true }, "electron-to-chromium": { - "version": "1.3.544", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.544.tgz", - "integrity": "sha512-jx6H7M1db76Q/dI3MadZC4qwNTvpiq8tdYEJswxexrIm5bH+LKRdg+VAteMF1tJJbBLrcuogE9N3nxT3Dp1gag==", + "version": "1.3.619", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.619.tgz", + "integrity": "sha512-WFGatwtk7Fw0QcKCZzfGD72hvbcXV8kLY8aFuj0Ip0QRnOtyLYMsc+wXbSjb2w4lk1gcAeNU1/lQ20A+tvuypQ==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, "node-releases": { - "version": "1.1.60", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz", - "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==", + "version": "1.1.67", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.67.tgz", + "integrity": "sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg==", "dev": true } } @@ -35434,6 +25641,17 @@ "upath": "^1.1.1" }, "dependencies": { + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -35902,39 +26120,12 @@ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.1.0.tgz", "integrity": "sha512-6S062WDQUXi6hOfkO/sBPVwE5ASXY4G2+b4atvhJfSsuUUhIaUKlkjLe9692Ipyt5/a+IPF5aVTu3V5gvXq5cg==" }, - "colornames": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", - "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=" - }, "colors": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=", "dev": true }, - "colorspace": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", - "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", - "dev": true, - "requires": { - "color": "3.0.x", - "text-hex": "1.0.x" - }, - "dependencies": { - "color": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", - "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", - "dev": true, - "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" - } - } - } - }, "columnify": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", @@ -36254,7 +26445,8 @@ "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true }, "consolidated-events": { "version": "2.0.2", @@ -36873,24 +27065,6 @@ "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==", "dev": true }, - "core-js-compat": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.4.tgz", - "integrity": "sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA==", - "dev": true, - "requires": { - "browserslist": "^4.8.3", - "semver": "7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true - } - } - }, "core-js-pure": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.2.1.tgz", @@ -36903,23 +27077,57 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cosmiconfig": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.5.tgz", - "integrity": "sha512-94j37OtvxS5w7qr7Ta6dt67tWdnOxigBVN4VnSxNXFez9o18PGQ0D33SchKP17r9LAcWVTYV72G6vDayAUBFIg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", + "dev": true, "requires": { - "is-directory": "^0.3.1", - "js-yaml": "^3.9.0", - "parse-json": "^4.0.0" + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" }, "dependencies": { + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "dev": true, "requires": { + "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" } + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "yaml": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", + "dev": true } } }, @@ -37350,6 +27558,40 @@ "cssnano-preset-default": "^4.0.7", "is-resolvable": "^1.0.0", "postcss": "^7.0.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } } }, "cssnano-preset-default": { @@ -37643,7 +27885,8 @@ "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true }, "deep-freeze": { "version": "0.0.1", @@ -37899,7 +28142,8 @@ "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true }, "denodeify": { "version": "1.2.1", @@ -37955,7 +28199,8 @@ "detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true }, "detect-newline": { "version": "3.1.0", @@ -37990,12 +28235,6 @@ } } }, - "devtools-protocol": { - "version": "0.0.818844", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.818844.tgz", - "integrity": "sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg==", - "dev": true - }, "dezalgo": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", @@ -38006,17 +28245,6 @@ "wrappy": "1" } }, - "diagnostics": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", - "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", - "dev": true, - "requires": { - "colorspace": "1.1.x", - "enabled": "1.0.x", - "kuler": "1.0.x" - } - }, "didyoumean": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.1.tgz", @@ -38112,6 +28340,7 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "dev": true, "requires": { "@babel/runtime": "^7.1.2" } @@ -38294,12 +28523,6 @@ "jsbn": "~0.1.0" } }, - "edge-paths": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.1.0.tgz", - "integrity": "sha512-ZpIN1Vm5hlo9dkkST/1s8QqPNne2uwk3Plf6HcVUhnpfal0WnDRLdNj/wdQo3xRc+wnN3C25wPpPlV2E6aOunQ==", - "dev": true - }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -38386,15 +28609,6 @@ } } }, - "enabled": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", - "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", - "dev": true, - "requires": { - "env-variable": "0.0.x" - } - }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -38461,11 +28675,6 @@ "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=", "dev": true }, - "env-variable": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz", - "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==" - }, "envinfo": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.5.0.tgz", @@ -39471,12 +29680,6 @@ "ext": "^1.1.2" } }, - "escalade": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz", - "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==", - "dev": true - }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -40809,12 +31012,11 @@ "dev": true }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -40966,9 +31168,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -41671,6 +31873,12 @@ } } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, "filelist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz", @@ -42154,6 +32362,7 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==", + "dev": true, "requires": { "minipass": "^2.2.1" } @@ -42176,148 +32385,13 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", - "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "optional": true, "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "^0.12.0" - }, - "dependencies": { - "chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", - "optional": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "node-pre-gyp": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", - "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "optional": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "optional": true - }, - "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "optional": true - } - } - }, - "fstream": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } + "bindings": "^1.5.0", + "nan": "^2.12.1" } }, "function-bind": { @@ -42357,6 +32431,7 @@ "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -42371,12 +32446,14 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -42385,6 +32462,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -42395,21 +32473,13 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, "requires": { "ansi-regex": "^2.0.0" } } } }, - "gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dev": true, - "requires": { - "globule": "^1.0.0" - } - }, "genfun": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz", @@ -42577,16 +32647,6 @@ "safe-buffer": "^5.1.1" } }, - "gifwrap": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.2.tgz", - "integrity": "sha512-fcIswrPaiCDAyO8xnWvHSZdWChjKXUanKKpAiWWJ/UTkEi/aYKn5+90e7DE820zbEaVR9CE2y4z9bzhQijZ0BA==", - "dev": true, - "requires": { - "image-q": "^1.1.1", - "omggif": "^1.0.10" - } - }, "git-raw-commits": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.0.tgz", @@ -43050,17 +33110,6 @@ "integrity": "sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM=", "dev": true }, - "globule": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.1.tgz", - "integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==", - "dev": true, - "requires": { - "glob": "~7.1.1", - "lodash": "~4.17.12", - "minimatch": "~3.0.2" - } - }, "gonzales-pe": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", @@ -43267,7 +33316,8 @@ "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true }, "has-value": { "version": "1.0.0", @@ -44076,25 +34126,20 @@ "dev": true }, "ignore-emit-webpack-plugin": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/ignore-emit-webpack-plugin/-/ignore-emit-webpack-plugin-2.0.3.tgz", - "integrity": "sha512-ahTYD5KZ3DiZG9goS8NCxBaPEfXsPLH5JeWKmFTThD8lsPen6R4tLnWcN/mrksK5cDqyxOzmRL12feJQZjffuA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/ignore-emit-webpack-plugin/-/ignore-emit-webpack-plugin-2.0.6.tgz", + "integrity": "sha512-/zC18RWCC2wz4ZwnS4UoujGWzvSKy28DLjtE+jrGBOXej6YdmityhBDzE8E0NlktEqi4tgdNbydX8B6G4haHSQ==", "dev": true }, "ignore-walk": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "dev": true, "requires": { "minimatch": "^3.0.4" } }, - "image-q": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/image-q/-/image-q-1.1.1.tgz", - "integrity": "sha1-/IQJlmRGC5DKhi2TALa/u7+/gFY=", - "dev": true - }, "image-size": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz", @@ -44220,12 +34265,6 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, - "in-publish": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz", - "integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==", - "dev": true - }, "indent-string": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", @@ -44261,7 +34300,8 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true }, "init-package-json": { "version": "1.10.3", @@ -44777,6 +34817,15 @@ "rgba-regex": "^1.0.0" } }, + "is-core-module": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz", + "integrity": "sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -45326,14 +35375,14 @@ "integrity": "sha1-elSbvZ/+FYWwzQoZHiAwVb7ldLQ=" }, "jest": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-25.3.0.tgz", - "integrity": "sha512-iKd5ShQSHzFT5IL/6h5RZJhApgqXSoPxhp5HEi94v6OAw9QkF8T7X+liEU2eEHJ1eMFYTHmeWLrpBWulsDpaUg==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest/-/jest-25.5.4.tgz", + "integrity": "sha512-hHFJROBTqZahnO+X+PMtT6G2/ztqAZJveGqz//FnWWHurizkD05PQGzRZOhF3XP6z7SJmL+5tCfW8qV06JypwQ==", "dev": true, "requires": { - "@jest/core": "^25.3.0", + "@jest/core": "^25.5.4", "import-local": "^3.0.2", - "jest-cli": "^25.3.0" + "jest-cli": "^25.5.4" }, "dependencies": { "@jest/console": { @@ -45380,12 +35429,11 @@ "dev": true }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -45576,12 +35624,6 @@ "pretty-format": "^25.5.0" } }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -45712,9 +35754,9 @@ } }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -45747,9 +35789,9 @@ "dev": true }, "yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, "requires": { "cliui": "^6.0.0", @@ -45762,7 +35804,7 @@ "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" + "yargs-parser": "^18.1.2" } }, "yargs-parser": { @@ -45801,12 +35843,11 @@ } }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -45836,9 +35877,9 @@ "dev": true }, "cross-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", - "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -45898,9 +35939,9 @@ } }, "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "requires": { "mimic-fn": "^2.1.0" @@ -45934,9 +35975,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -46029,12 +36070,11 @@ "dev": true }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -46077,27 +36117,6 @@ "test-exclude": "^6.0.0" } }, - "babel-plugin-jest-hoist": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.5.0.tgz", - "integrity": "sha512-u+/W+WAjMlvoocYGTwthAiQSxDcJAyHpQ6oWlHdFZaaN+Rlk8Q7iiwDPg2lN/FyJtAYnKjFxbn7xus4HCFkg5g==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-jest": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-25.5.0.tgz", - "integrity": "sha512-8ZczygctQkBU+63DtSOKGh7tFL0CeCuz+1ieud9lJ1WPQ9O6A1a/r+LGn6Y705PA6whHQ3T1XuB/PmpfNYf8Fw==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^25.5.0", - "babel-preset-current-node-syntax": "^0.1.2" - } - }, "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -46164,9 +36183,9 @@ } }, "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.2.1.tgz", + "integrity": "sha512-bTLYHSeC0UH/EFXS9KqWnXuOl/wHK5Z/d+ghd5AsFMYN7wIGkUCOJyzy88+wJKkZPGON8u4Z9f6U4FdgURE9qA==", "dev": true, "optional": true }, @@ -46284,12 +36303,6 @@ "supports-color": "^7.0.0" } }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -46358,14 +36371,14 @@ "dev": true }, "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -46437,11 +36450,12 @@ "dev": true }, "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", "dev": true, "requires": { + "is-core-module": "^2.1.0", "path-parse": "^1.0.6" } }, @@ -46464,9 +36478,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -46730,12 +36744,11 @@ "dev": true }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -46814,9 +36827,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -46889,12 +36902,11 @@ } }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -47014,9 +37026,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -47073,12 +37085,11 @@ } }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -47204,9 +37215,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -47416,12 +37427,11 @@ "dev": true }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -47586,9 +37596,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -47745,12 +37755,11 @@ "dev": true }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -47810,9 +37819,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -48125,12 +38134,11 @@ } }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -48172,9 +38180,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -48247,12 +38255,11 @@ } }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -48320,9 +38327,9 @@ } }, "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.2.1.tgz", + "integrity": "sha512-bTLYHSeC0UH/EFXS9KqWnXuOl/wHK5Z/d+ghd5AsFMYN7wIGkUCOJyzy88+wJKkZPGON8u4Z9f6U4FdgURE9qA==", "dev": true, "optional": true }, @@ -48498,14 +38505,14 @@ "dev": true }, "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -48559,11 +38566,12 @@ "dev": true }, "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", "dev": true, "requires": { + "is-core-module": "^2.1.0", "path-parse": "^1.0.6" } }, @@ -48580,9 +38588,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -48733,12 +38741,11 @@ "dev": true }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -48842,9 +38849,9 @@ } }, "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.2.1.tgz", + "integrity": "sha512-bTLYHSeC0UH/EFXS9KqWnXuOl/wHK5Z/d+ghd5AsFMYN7wIGkUCOJyzy88+wJKkZPGON8u4Z9f6U4FdgURE9qA==", "dev": true, "optional": true }, @@ -49013,12 +39020,6 @@ "supports-color": "^7.0.0" } }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -49087,14 +39088,14 @@ "dev": true }, "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -49172,11 +39173,12 @@ "dev": true }, "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", "dev": true, "requires": { + "is-core-module": "^2.1.0", "path-parse": "^1.0.6" } }, @@ -49225,9 +39227,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -49287,9 +39289,9 @@ "dev": true }, "yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, "requires": { "cliui": "^6.0.0", @@ -49302,7 +39304,7 @@ "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" + "yargs-parser": "^18.1.2" } }, "yargs-parser": { @@ -49390,12 +39392,11 @@ "dev": true }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -49597,14 +39598,14 @@ "dev": true }, "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -49676,11 +39677,12 @@ "dev": true }, "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", "dev": true, "requires": { + "is-core-module": "^2.1.0", "path-parse": "^1.0.6" } }, @@ -49697,9 +39699,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -50129,12 +40131,11 @@ } }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -50245,9 +40246,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -50299,12 +40300,6 @@ "resolved": "https://registry.npmjs.org/jetifier/-/jetifier-1.6.5.tgz", "integrity": "sha512-T7yzBSu9PR+DqjYt+I0KVO1XTb1QhAfHnXV5Nd3xpbXM6Xg4e3vP60Q4qkNU8Fh6PHC2PivPUNN3rY7G2MxcDQ==" }, - "js-base64": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz", - "integrity": "sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ==", - "dev": true - }, "js-string-escape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", @@ -50729,15 +40724,6 @@ "integrity": "sha512-eYboRV94Vco725nKMlpkn3nV2+96p9c3gKXRsYqAJSswSENvBhN7n5L+uDhY58xQa0UukWsDMTGELzmD8Q+wTA==", "dev": true }, - "kuler": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", - "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", - "dev": true, - "requires": { - "colornames": "^1.1.1" - } - }, "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -50813,23 +40799,6 @@ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true }, - "levenary": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", - "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", - "dev": true, - "requires": { - "leven": "^3.1.0" - }, - "dependencies": { - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - } - } - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -52131,12 +42100,6 @@ "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", "dev": true }, - "md5-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-4.0.0.tgz", - "integrity": "sha512-UC0qFwyAjn4YdPpKaDNw6gNxRf7Mcx7jC1UGCY4boCzgvU2Aoc1mOGzTtrjjLKhM5ivsnhoKpQVxKPp+1j1qwg==", - "dev": true - }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -53082,6 +43045,37 @@ "metro-cache": "^0.56.4", "metro-core": "^0.56.4", "pretty-format": "^24.7.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } } }, "metro-core": { @@ -53547,6 +43541,7 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "dev": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -53555,7 +43550,8 @@ "yallist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true } } }, @@ -53641,6 +43637,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "dev": true, "requires": { "minipass": "^2.2.1" } @@ -53741,6 +43738,14 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.1.tgz", "integrity": "sha512-shJkRTSebXvsVqk56I+lkb2latjBs8I+pc2TzWc545y2iFnSjm7Wg0QMh+ZWcdSLQyGEau5jI8ocnmkyTgr9YQ==" }, + "moment-timezone": { + "version": "0.5.31", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", + "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", + "requires": { + "moment": ">= 2.9.0" + } + }, "moo": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/moo/-/moo-0.4.3.tgz", @@ -53853,9 +43858,9 @@ } }, "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" }, "nanoid": { "version": "3.1.16", @@ -53908,9 +43913,10 @@ } }, "needle": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", - "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.2.tgz", + "integrity": "sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==", + "dev": true, "requires": { "debug": "^3.2.6", "iconv-lite": "^0.4.4", @@ -53921,6 +43927,7 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, "requires": { "ms": "^2.1.1" } @@ -53928,7 +43935,8 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, @@ -54027,12 +44035,11 @@ } }, "node-gyp": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", - "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-4.0.0.tgz", + "integrity": "sha512-2XiryJ8sICNo6ej8d0idXDEMKfVfFK7kekGCtJAuelGsYHQxhj13KTf95swTCN2dZ/4lTfZ84Fu31jqJEEgjWA==", "dev": true, "requires": { - "fstream": "^1.0.0", "glob": "^7.0.3", "graceful-fs": "^4.1.2", "mkdirp": "^0.5.0", @@ -54042,7 +44049,7 @@ "request": "^2.87.0", "rimraf": "2", "semver": "~5.3.0", - "tar": "^2.0.0", + "tar": "^4.4.8", "which": "1" }, "dependencies": { @@ -54165,9 +44172,9 @@ }, "dependencies": { "chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true }, "glob": { @@ -54195,9 +44202,9 @@ } }, "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", "dev": true, "requires": { "abbrev": "1", @@ -54248,187 +44255,29 @@ "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==", "dev": true }, - "node-sass": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.1.tgz", - "integrity": "sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw==", - "dev": true, - "requires": { - "async-foreach": "^0.1.3", - "chalk": "^1.1.1", - "cross-spawn": "^3.0.0", - "gaze": "^1.0.0", - "get-stdin": "^4.0.1", - "glob": "^7.0.3", - "in-publish": "^2.0.0", - "lodash": "^4.17.15", - "meow": "^3.7.0", - "mkdirp": "^0.5.1", - "nan": "^2.13.2", - "node-gyp": "^3.8.0", - "npmlog": "^4.0.0", - "request": "^2.88.0", - "sass-graph": "^2.2.4", - "stdout-stream": "^1.4.0", - "true-case-path": "^1.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true - }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true, - "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - } - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "cross-spawn": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", - "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" - } - }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, - "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - } - }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true, - "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true - } - } - }, "node-watch": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.6.0.tgz", - "integrity": "sha512-XAgTL05z75ptd7JSVejH1a2Dm1zmXYhuDr9l230Qk6Z7/7GPcnAs/UyJJ4ggsXSvWil8iOzwQLW0zuGUvHpG8g==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.7.0.tgz", + "integrity": "sha512-OOBiglke5SlRQT5WYfwXTmYqTfXjcTNBHpalyHLtLxDpQYVpVRkJqabcch1kmwJsjV/J4OZuzEafeb4soqtFZA==", "dev": true }, "nodegit": { - "version": "0.26.2", - "resolved": "https://registry.npmjs.org/nodegit/-/nodegit-0.26.2.tgz", - "integrity": "sha512-wf3Su4+kyAeN1fMMg/rl66sb+J2W+RFDYbfzLy4/ZGCS1V/DRd1YMWcXiu6JpYm6x9/6MobVAO+9aHQdC5i8Pw==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/nodegit/-/nodegit-0.27.0.tgz", + "integrity": "sha512-E9K4gPjWiA0b3Tx5lfWCzG7Cvodi2idl3V5UD2fZrOrHikIfrN7Fc2kWLtMUqqomyoToYJLeIC8IV7xb1CYRLA==", "dev": true, "requires": { "fs-extra": "^7.0.0", + "got": "^10.7.0", "json5": "^2.1.0", "lodash": "^4.17.14", "nan": "^2.14.0", "node-gyp": "^4.0.0", "node-pre-gyp": "^0.13.0", - "promisify-node": "~0.3.0", "ramda": "^0.25.0", - "request-promise-native": "^1.0.5", "tar-fs": "^1.16.3" }, "dependencies": { - "chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", - "dev": true - }, "fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -54441,108 +44290,16 @@ } }, "json5": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", - "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "minipass": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.8.6.tgz", - "integrity": "sha512-lFG7d6g3+/UaFDCOtqPiKAC9zngWWsQZl1g5q6gaONqrjq61SX2xFqXMleQiFVyDpYwa018E9hmlAFY22PCb+A==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "node-gyp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-4.0.0.tgz", - "integrity": "sha512-2XiryJ8sICNo6ej8d0idXDEMKfVfFK7kekGCtJAuelGsYHQxhj13KTf95swTCN2dZ/4lTfZ84Fu31jqJEEgjWA==", - "dev": true, - "requires": { - "glob": "^7.0.3", - "graceful-fs": "^4.1.2", - "mkdirp": "^0.5.0", - "nopt": "2 || 3", - "npmlog": "0 || 1 || 2 || 3 || 4", - "osenv": "0", - "request": "^2.87.0", - "rimraf": "2", - "semver": "~5.3.0", - "tar": "^4.4.8", - "which": "1" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - } - } - }, - "semver": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", - "dev": true - }, - "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", "dev": true, "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" + "minimist": "^1.2.5" } - }, - "yallist": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.0.tgz", - "integrity": "sha512-6gpP93MR+VOOehKbCPchro3wFZNSNmek8A2kbkOAZLIZAYx1KP/zAqwO0sOHi3xJEb+UBz8NaYt/17UNit1Q9w==", - "dev": true } } }, - "nodegit-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/nodegit-promise/-/nodegit-promise-4.0.0.tgz", - "integrity": "sha1-VyKxhPLfcycWEGSnkdLoQskWezQ=", - "dev": true, - "requires": { - "asap": "~2.0.3" - } - }, "nomnom": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.6.2.tgz", @@ -54609,7 +44366,8 @@ "npm-bundled": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", - "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==" + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", + "dev": true }, "npm-lifecycle": { "version": "3.1.4", @@ -55252,6 +45010,7 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.4.tgz", "integrity": "sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==", + "dev": true, "requires": { "ignore-walk": "^3.0.1", "npm-bundled": "^1.0.1" @@ -55300,6 +45059,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -55597,12 +45357,6 @@ "integrity": "sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==", "dev": true }, - "omggif": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", - "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", - "dev": true - }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -55790,7 +45544,8 @@ "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true }, "os-locale": { "version": "2.1.0", @@ -55842,6 +45597,7 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, "requires": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" @@ -56352,15 +46108,6 @@ "find-up": "^2.1.0" } }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, "platform": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", @@ -56804,6 +46551,40 @@ "requires": { "cosmiconfig": "^5.0.0", "import-cwd": "^2.0.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } } }, "postcss-loader": { @@ -57399,9 +47180,9 @@ "dev": true }, "prettier": { - "version": "npm:wp-prettier@2.0.5", - "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-2.0.5.tgz", - "integrity": "sha512-5GCgdeevIXwR3cW4Qj5XWC5MO1iSCz8+IPn0mMw6awAt/PBiey8yyO7MhePRsaMqghJAhg6Q3QLYWSnUHWkG6A==", + "version": "npm:wp-prettier@2.2.1-beta-1", + "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-2.2.1-beta-1.tgz", + "integrity": "sha512-+JHkqs9LC/JPp51yy1hzs3lQ7qeuWCwOcSzpQNeeY/G7oSpnF61vxt7hRh87zNRTr6ob2ndy0W8rVzhgrcA+Gw==", "dev": true }, "prettier-linter-helpers": { @@ -57754,15 +47535,6 @@ } } }, - "promisify-node": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/promisify-node/-/promisify-node-0.3.0.tgz", - "integrity": "sha1-tLVaz5D6p9K4uQyjlomQhsAwYM8=", - "dev": true, - "requires": { - "nodegit-promise": "~4.0.0" - } - }, "prompts": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.1.tgz", @@ -58113,9 +47885,9 @@ "dev": true }, "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, "bl": { @@ -58138,9 +47910,9 @@ } }, "buffer": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.0.tgz", - "integrity": "sha512-cd+5r1VLBwUqTrmnzW+D7ABkJUM6mr7uv1dv+6jRw4Rcl7tFIFHDqHPL98LhpGFn3dbAt3gtLxtrWp4m1kFrqg==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, "requires": { "base64-js": "^1.3.1", @@ -58154,9 +47926,9 @@ "dev": true }, "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -58242,15 +48014,15 @@ } }, "tar-fs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz", - "integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "dev": true, "requires": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.0.0" + "tar-stream": "^2.1.4" } }, "tar-stream": { @@ -58267,9 +48039,9 @@ } }, "ws": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", - "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", + "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==", "dev": true }, "yauzl": { @@ -58293,7 +48065,8 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true }, "query-string": { "version": "4.3.4", @@ -58520,6 +48293,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -58555,11 +48329,11 @@ } }, "react-autosize-textarea": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/react-autosize-textarea/-/react-autosize-textarea-3.0.2.tgz", - "integrity": "sha1-K2hApp9xOHGavOpaQp7PcwF2jAc=", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/react-autosize-textarea/-/react-autosize-textarea-7.1.0.tgz", + "integrity": "sha512-BHpjCDkuOlllZn3nLazY2F8oYO1tS2jHnWhcjTWQdcKiiMU6gHLNt/fzmqMSyerR0eTdKtfSIqtSeTtghNwS+g==", "requires": { - "autosize": "^4.0.0", + "autosize": "^4.0.2", "line-height": "^0.3.1", "prop-types": "^15.5.6" } @@ -58639,6 +48413,15 @@ "text-table": "0.2.0" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, "ansi-escapes": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", @@ -59582,7 +49365,8 @@ "react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "dev": true }, "react-merge-refs": { "version": "1.0.0", @@ -60861,6 +50645,7 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "dev": true, "requires": { "dom-helpers": "^3.4.0", "loose-envify": "^1.4.0", @@ -61028,15 +50813,6 @@ "util-deprecate": "~1.0.1" } }, - "readdir-glob": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", - "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", - "dev": true, - "requires": { - "minimatch": "^3.0.4" - } - }, "readdir-scoped-modules": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", @@ -61358,19 +51134,6 @@ "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", "dev": true }, - "regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - } - }, "regextras": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.7.1.tgz", @@ -61811,26 +51574,6 @@ } } }, - "request-promise-core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", - "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", - "dev": true, - "requires": { - "lodash": "^4.13.1" - } - }, - "request-promise-native": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", - "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", - "dev": true, - "requires": { - "request-promise-core": "1.1.1", - "stealthy-require": "^1.1.0", - "tough-cookie": ">=2.3.3" - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -62239,115 +51982,13 @@ } } }, - "sass-graph": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", - "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "sass": { + "version": "1.26.11", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.26.11.tgz", + "integrity": "sha512-W1l/+vjGjIamsJ6OnTe0K37U2DBO/dgsv2Z4c89XQ8ZOO6l/VwkqwLSqoYzJeJs6CLuGSTRWc91GbQFL3lvrvw==", "dev": true, "requires": { - "glob": "^7.0.0", - "lodash": "^4.0.0", - "scss-tokenizer": "^0.2.3", - "yargs": "^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, - "requires": { - "lcid": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true - }, - "yargs": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", - "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", - "dev": true, - "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.0" - } - }, - "yargs-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", - "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", - "dev": true, - "requires": { - "camelcase": "^3.0.0" - } - } + "chokidar": ">=2.0.0 <4.0.0" } }, "sass-loader": { @@ -62471,27 +52112,6 @@ "ajv-keywords": "^3.1.0" } }, - "scss-tokenizer": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", - "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", - "dev": true, - "requires": { - "js-base64": "^2.1.8", - "source-map": "^0.4.2" - }, - "dependencies": { - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true, - "requires": { - "amdefine": ">=0.0.4" - } - } - } - }, "select": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", @@ -63463,15 +53083,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" }, - "stdout-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", - "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - } - }, "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", @@ -63509,15 +53120,6 @@ "stream-shift": "^1.0.0" } }, - "stream-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-equal/-/stream-equal-1.1.1.tgz", - "integrity": "sha512-SaZxkvxujYBR6NTumhRTg/yztw2p30fzZ/jvSgQtlZFEGI7tdSNDaPbvT47QF92hx6Tar8hAhpr7ErpTNvtuCQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "stream-http": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", @@ -64398,7 +54000,8 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true }, "strong-log-transformer": { "version": "2.1.0", @@ -64609,18 +54212,6 @@ "fill-range": "^7.0.1" } }, - "browserslist": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz", - "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001043", - "electron-to-chromium": "^1.3.413", - "node-releases": "^1.1.53", - "pkg-up": "^2.0.0" - } - }, "caniuse-lite": { "version": "1.0.30001081", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001081.tgz", @@ -64652,8 +54243,7 @@ "electron-to-chromium": { "version": "1.3.468", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.468.tgz", - "integrity": "sha512-+KAppdklzPd5v8nLOvtDiD/S67mCT9gFRAvngYe8zuFy9azHhT9vWWH6WEPPCcyjQ1JMYVgqbN29yZ0paqxsEw==", - "dev": true + "integrity": "sha512-+KAppdklzPd5v8nLOvtDiD/S67mCT9gFRAvngYe8zuFy9azHhT9vWWH6WEPPCcyjQ1JMYVgqbN29yZ0paqxsEw==" }, "emoji-regex": { "version": "8.0.0", @@ -64774,8 +54364,7 @@ "node-releases": { "version": "1.1.58", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.58.tgz", - "integrity": "sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg==", - "dev": true + "integrity": "sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg==" }, "parse-json": { "version": "5.0.0", @@ -65403,14 +54992,42 @@ "dev": true }, "tar": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", - "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", "dev": true, "requires": { - "block-stream": "*", - "fstream": "^1.0.12", - "inherits": "2" + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "dependencies": { + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } } }, "tar-fs": { @@ -65989,12 +55606,6 @@ "integrity": "sha512-F91ZqLgvi1E0PdvmxMgp+gcf6q8fMH7mhdwWfzXnl1k+GbpQDmi8l7DzLC5JTASKbwpY3TfxajAUzAXcv2NmsQ==", "dev": true }, - "text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", - "dev": true - }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -66278,15 +55889,6 @@ "integrity": "sha512-fwkLWH+DimvA4YCy+/nvJd61nWQQ2liO/nF/RjkTpiOGi+zxZzVkhb1mvbHIIW4b/8nDsYI8uTmAlc0nNkRMOw==", "dev": true }, - "true-case-path": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", - "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", - "dev": true, - "requires": { - "glob": "^7.1.2" - } - }, "tryer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", @@ -66532,15 +56134,15 @@ }, "dependencies": { "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, "buffer": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.0.tgz", - "integrity": "sha512-cd+5r1VLBwUqTrmnzW+D7ABkJUM6mr7uv1dv+6jRw4Rcl7tFIFHDqHPL98LhpGFn3dbAt3gtLxtrWp4m1kFrqg==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, "requires": { "base64-js": "^1.3.1", @@ -67018,9 +56620,9 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.2.tgz", - "integrity": "sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw==" + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==" }, "v8-compile-cache": { "version": "2.1.0", @@ -67331,12 +56933,6 @@ } } }, - "walkdir": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.11.tgz", - "integrity": "sha1-oW0CXrkxvQO1LzCMrtD0D86+lTI=", - "dev": true - }, "walker": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", @@ -68339,6 +57935,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, "requires": { "string-width": "^1.0.2 || 2" } diff --git a/package.json b/package.json index e79d6fcec27fef..ecec9a29644009 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "9.2.0-rc.1", + "version": "9.6.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", @@ -82,10 +82,10 @@ "devDependencies": { "@actions/core": "1.2.6", "@actions/github": "1.0.0", - "@babel/core": "7.11.6", - "@babel/plugin-syntax-jsx": "7.10.4", - "@babel/runtime-corejs3": "7.11.2", - "@babel/traverse": "7.11.5", + "@babel/core": "7.12.9", + "@babel/plugin-syntax-jsx": "7.12.1", + "@babel/runtime-corejs3": "7.12.5", + "@babel/traverse": "7.12.9", "@jest/types": "25.3.0", "@octokit/rest": "16.26.0", "@octokit/webhooks": "7.1.0", @@ -106,7 +106,8 @@ "@types/requestidlecallback": "0.3.1", "@types/semver": "7.2.0", "@types/sprintf-js": "1.1.2", - "@types/uuid": "7.0.2", + "@types/tinycolor2": "1.4.2", + "@types/uuid": "8.3.0", "@types/webpack": "4.41.16", "@types/webpack-sources": "0.1.7", "@wordpress/babel-plugin-import-jsx-pragma": "file:packages/babel-plugin-import-jsx-pragma", @@ -133,7 +134,7 @@ "@wordpress/prettier-config": "file:packages/prettier-config", "@wordpress/project-management-automation": "file:packages/project-management-automation", "@wordpress/scripts": "file:packages/scripts", - "appium": "1.17.1", + "appium": "1.18.3", "babel-jest": "25.3.0", "babel-loader": "8.1.0", "babel-plugin-emotion": "10.0.33", @@ -142,7 +143,7 @@ "babel-plugin-react-native-platform-specific-extensions": "1.1.1", "babel-plugin-require-context-hook": "1.0.0", "benchmark": "2.1.4", - "browserslist": "4.14.0", + "browserslist": "4.15.0", "chalk": "4.0.0", "commander": "4.1.0", "concurrently": "3.5.0", @@ -160,7 +161,7 @@ "glob": "7.1.2", "husky": "3.0.5", "inquirer": "7.1.0", - "jest": "25.3.0", + "jest": "25.5.4", "jest-emotion": "10.0.32", "jest-junit": "10.0.0", "jest-serializer-enzyme": "1.0.0", @@ -174,12 +175,11 @@ "metro-react-native-babel-transformer": "0.56.0", "mkdirp": "0.5.1", "nock": "12.0.3", - "node-sass": "4.13.1", - "node-watch": "0.6.0", + "node-watch": "0.7.0", "patch-package": "6.2.2", "postcss": "7.0.32", "postcss-loader": "3.0.0", - "prettier": "npm:wp-prettier@2.0.5", + "prettier": "npm:wp-prettier@2.2.1-beta-1", "progress": "2.0.3", "puppeteer": "npm:puppeteer-core@3.0.0", "react": "16.13.1", @@ -188,6 +188,7 @@ "react-test-renderer": "16.13.1", "rimraf": "3.0.2", "rtlcss": "2.4.0", + "sass": "1.26.11", "sass-loader": "8.0.2", "semver": "7.3.2", "simple-git": "1.113.0", @@ -197,7 +198,7 @@ "stylelint-config-wordpress": "17.0.0", "terser-webpack-plugin": "3.0.3", "typescript": "4.0.3", - "uuid": "7.0.2", + "uuid": "8.3.0", "wd": "1.12.1", "webpack": "4.42.0", "worker-farm": "1.7.0" @@ -222,6 +223,7 @@ "fixtures:generate": "cross-env GENERATE_MISSING_FIXTURES=y npm run test-unit", "fixtures:regenerate": "npm run fixtures:clean && npm run fixtures:generate", "format-js": "wp-scripts format-js", + "format-php": "wp-env run composer run-script format", "lint": "concurrently \"npm run lint-lockfile\" \"npm run lint-js\" \"npm run lint-pkg-json\" \"npm run lint-css\"", "lint-js": "wp-scripts lint-js", "lint-js:fix": "npm run lint-js -- --fix", @@ -247,7 +249,9 @@ "test-e2e:watch": "npm run test-e2e -- --watch", "test-performance": "wp-scripts test-e2e --config packages/e2e-tests/jest.performance.config.js", "test-php": "npm run lint-php && npm run test-unit-php", + "test-php:watch": "wp-env run composer run-script test:watch", "test-unit": "wp-scripts test-unit-js --config test/unit/jest.config.js", + "test-unit:date": "./bin/unit-test-date.sh", "test-unit:debug": "wp-scripts --inspect-brk test-unit-js --runInBand --no-cache --verbose --config test/unit/jest.config.js ", "test-unit:update": "npm run test-unit -- --updateSnapshot", "test-unit:watch": "npm run test-unit -- --watch", @@ -266,7 +270,7 @@ "playground:build": "npm run storybook:build", "playground:dev": "echo \"Please use storybook:dev instead.\"", "env": "wp-scripts env", - "wp-env": "packages/env/bin/wp-env" + "wp-env": "wp-env" }, "husky": { "hooks": { diff --git a/packages/README.md b/packages/README.md index 62609309bce59d..eaf6f9fee0d5a1 100644 --- a/packages/README.md +++ b/packages/README.md @@ -27,7 +27,7 @@ When creating a new package, you need to provide at least the following: "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.11.2" + "@babel/runtime": "^7.12.5" }, "publishConfig": { "access": "public" @@ -308,17 +308,4 @@ If your package includes a few files with side effects, you can list them instea } ``` -Many `@wordpress` UI-focused packages rely on side effects for registering blocks, plugins, and data stores. To reduce maintenance costs, it may be preferable to opt for an inverse glob strategy, where you instead list the paths where side effects are *not* present, leaving the bundler to assume that everything else might have them. This results in a glob with multiple roots (to match `@wordpress` package structure) and one or more excluded directories. - -Here is an example where we declare that the `components` and `utils` directories are side effect-free: - -```json -{ - "name": "package", - "sideEffects": [ - "!((src|build|build-module)/(components|utils)/**)" - ], -} -``` - Please consult the [side effects documentation](./side-effects.md) for more information on identifying and declaring side effects. diff --git a/packages/a11y/package.json b/packages/a11y/package.json index 4d4be5cdbfa660..dfe0b45972802d 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -25,7 +25,7 @@ "react-native": "src/index", "types": "build-types", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/dom-ready": "file:../dom-ready", "@wordpress/i18n": "file:../i18n" }, diff --git a/packages/annotations/CHANGELOG.md b/packages/annotations/CHANGELOG.md index c7aa454520ff3d..ac05be83598b2e 100644 --- a/packages/annotations/CHANGELOG.md +++ b/packages/annotations/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### New Feature + +- Added a store definition `store` for the annotations namespace to use with `@wordpress/data` API ([#26655](https://github.com/WordPress/gutenberg/pull/26655)). + ## 1.0.5 (2019-01-03) ## 1.0.4 (2018-12-12) @@ -16,4 +20,4 @@ ### New Features -- Implement annotations API in the editor. +- Implement annotations API in the editor. diff --git a/packages/annotations/package.json b/packages/annotations/package.json index bde365367403fa..4355a85792e0d3 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -22,14 +22,14 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/data": "file:../data", "@wordpress/hooks": "file:../hooks", "@wordpress/i18n": "file:../i18n", "@wordpress/rich-text": "file:../rich-text", "lodash": "^4.17.19", "rememo": "^3.0.0", - "uuid": "^7.0.2" + "uuid": "^8.3.0" }, "publishConfig": { "access": "public" diff --git a/packages/annotations/src/format/annotation.js b/packages/annotations/src/format/annotation.js index 9ce2a2bd6d456f..f3ea7096a37827 100644 --- a/packages/annotations/src/format/annotation.js +++ b/packages/annotations/src/format/annotation.js @@ -7,7 +7,7 @@ import { applyFormat, removeFormat } from '@wordpress/rich-text'; const FORMAT_NAME = 'core/annotation'; const ANNOTATION_ATTRIBUTE_PREFIX = 'annotation-text-'; -const STORE_KEY = 'core/annotations'; +const STORE_NAME = 'core/annotations'; /** * Applies given annotations to the given record. @@ -145,7 +145,7 @@ export const annotation = { ) { return { annotations: select( - STORE_KEY + STORE_NAME ).__experimentalGetAnnotationsForRichText( blockClientId, richTextIdentifier @@ -165,9 +165,9 @@ export const annotation = { }, __experimentalGetPropsForEditableTreeChangeHandler( dispatch ) { return { - removeAnnotation: dispatch( STORE_KEY ) + removeAnnotation: dispatch( STORE_NAME ) .__experimentalRemoveAnnotation, - updateAnnotationRange: dispatch( STORE_KEY ) + updateAnnotationRange: dispatch( STORE_NAME ) .__experimentalUpdateAnnotationRange, }; }, diff --git a/packages/annotations/src/index.js b/packages/annotations/src/index.js index 9435b1d8616616..4624351fd98a6e 100644 --- a/packages/annotations/src/index.js +++ b/packages/annotations/src/index.js @@ -1,6 +1,7 @@ /** * Internal dependencies */ -import './store'; import './format'; import './block'; + +export { store } from './store'; diff --git a/packages/annotations/src/store/index.js b/packages/annotations/src/store/index.js index 1c7f27dccbc351..81dee6efd82b44 100644 --- a/packages/annotations/src/store/index.js +++ b/packages/annotations/src/store/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { registerStore } from '@wordpress/data'; +import { register, createReduxStore } from '@wordpress/data'; /** * Internal dependencies @@ -13,12 +13,19 @@ import * as actions from './actions'; /** * Module Constants */ -const MODULE_KEY = 'core/annotations'; +const STORE_NAME = 'core/annotations'; -const store = registerStore( MODULE_KEY, { +/** + * Store definition for the annotations namespace. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore + * + * @type {Object} + */ +export const store = createReduxStore( STORE_NAME, { reducer, selectors, actions, } ); -export default store; +register( store ); diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index 8f5225db3178bb..ac977d28b4315e 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -23,7 +23,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/i18n": "file:../i18n", "@wordpress/url": "file:../url" }, diff --git a/packages/autop/package.json b/packages/autop/package.json index 46d41447a9d5f3..4fe3f744dfeb49 100644 --- a/packages/autop/package.json +++ b/packages/autop/package.json @@ -24,7 +24,7 @@ "types": "build-types", "sideEffects": false, "dependencies": { - "@babel/runtime": "^7.11.2" + "@babel/runtime": "^7.12.5" }, "publishConfig": { "access": "public" diff --git a/packages/babel-plugin-makepot/package.json b/packages/babel-plugin-makepot/package.json index 6b44ddfbe85352..0d53f4c65093cd 100644 --- a/packages/babel-plugin-makepot/package.json +++ b/packages/babel-plugin-makepot/package.json @@ -30,7 +30,7 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "gettext-parser": "^1.3.1", "lodash": "^4.17.19" }, diff --git a/packages/babel-preset-default/CHANGELOG.md b/packages/babel-preset-default/CHANGELOG.md index a45e8107796925..7a2e33780170f5 100644 --- a/packages/babel-preset-default/CHANGELOG.md +++ b/packages/babel-preset-default/CHANGELOG.md @@ -2,7 +2,13 @@ ## Unreleased -## Breaking Changes +### New Features + +- The bundled `@babel/core` dependency has been updated from requiring `^7.11.6` to requiring `^7.12.9`. All other Babel plugins were updated to the latest version (see [Highlights](https://babeljs.io/blog/2020/10/15/7.12.0)). + +## 4.14.0 (2020-05-27) + +### Breaking Changes - Revert enabling the `shippedProposals` flag. That flag enables the use of stage-3 proposals, but the goal of this preset is to only support stage-4 features. [#22083](https://github.com/WordPress/gutenberg/pull/22083) diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index 38fac877c8f836..131fc3aa68e88b 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -28,11 +28,11 @@ ], "main": "index.js", "dependencies": { - "@babel/core": "^7.11.6", - "@babel/plugin-transform-react-jsx": "^7.10.4", - "@babel/plugin-transform-runtime": "^7.11.5", - "@babel/preset-env": "^7.11.5", - "@babel/runtime": "^7.11.2", + "@babel/core": "^7.12.9", + "@babel/plugin-transform-react-jsx": "^7.12.7", + "@babel/plugin-transform-runtime": "^7.12.1", + "@babel/preset-env": "^7.12.7", + "@babel/runtime": "^7.12.5", "@wordpress/babel-plugin-import-jsx-pragma": "file:../babel-plugin-import-jsx-pragma", "@wordpress/browserslist-config": "file:../browserslist-config", "@wordpress/element": "file:../element", diff --git a/packages/base-styles/README.md b/packages/base-styles/README.md index b4ad22facfce83..8e32525f9a4beb 100644 --- a/packages/base-styles/README.md +++ b/packages/base-styles/README.md @@ -23,6 +23,7 @@ In your application's SCSS file, include styles like so: @import "node_modules/@wordpress/base-styles/breakpoints"; @import "node_modules/@wordpress/base-styles/animations"; @import "node_modules/@wordpress/base-styles/z-index"; +@import 'node_modules/@wordpress/base-styles/default-custom-properties'; ``` If you use [Webpack](https://webpack.js.org/) for your SCSS pipeline, you can use `~` to resolve to `node_modules`: diff --git a/packages/base-styles/_colors.native.scss b/packages/base-styles/_colors.native.scss index f4a0f6a05f5558..3e63958cc4624a 100644 --- a/packages/base-styles/_colors.native.scss +++ b/packages/base-styles/_colors.native.scss @@ -40,6 +40,7 @@ $gray: #87a6bc; $gray-light: lighten($gray, 33%); //#f3f6f8 $gray-dark: darken($gray, 38%); //#2e4453 $gray-900: #1a1a1a; +$gray-700: #757575; // $gray-text: ideal for standard, non placeholder text // $gray-text-min: minimum contrast needed for WCAG 2.0 AA on white background diff --git a/packages/base-styles/_mixins.scss b/packages/base-styles/_mixins.scss index 30890d669ce0c9..dc8968dab8daa7 100644 --- a/packages/base-styles/_mixins.scss +++ b/packages/base-styles/_mixins.scss @@ -208,6 +208,10 @@ color: #555; font-size: $default-font-size; text-align: center; + + .is-dark-theme & { + color: $light-gray-placeholder; + } } @@ -354,20 +358,26 @@ margin-right: $grid-unit-15; transition: none; border-radius: $radius-round; + width: $radio-input-size-sm; + height: $radio-input-size-sm; + + @include break-small() { + height: $radio-input-size; + width: $radio-input-size; + } &:checked::before { - width: 7px; - height: 7px; - margin: 8px 0 0 8px; + width: 8px; + height: 8px; + transform: translate(7px, 7px); + margin: 0; background-color: $white; // This border serves as a background color in Windows High Contrast mode. - border: 3px solid $white; + border: 4px solid $white; - @include break-medium() { - width: 6px; - height: 6px; - margin: 4px 0 0 4px; + @include break-small() { + transform: translate(5px, 5px); } } @@ -677,3 +687,21 @@ } /* stylelint-enable function-comma-space-after */ } + +/** + * These are default block editor widths in case the theme doesn't provide them. + */ +@mixin default-block-widths { + + .wp-block { + max-width: $content-width; + + &[data-align="wide"] { + max-width: $wide-content-width; + } + + &[data-align="full"] { + max-width: none; + } + } +} diff --git a/packages/base-styles/_variables.scss b/packages/base-styles/_variables.scss index c55cc42da07837..a8f22314dfc430 100644 --- a/packages/base-styles/_variables.scss +++ b/packages/base-styles/_variables.scss @@ -1,3 +1,10 @@ +/** + * SCSS Variables. + * + * Please use variables from this sheet to ensure consistency across the UI. + * Don't add to this sheet unless you're pretty sure the value will be reused in many places. + */ + @import "./colors"; /** @@ -10,15 +17,11 @@ $default-line-height: 1.4; $editor-font: "Noto Serif", serif; $editor-html-font: Menlo, Consolas, monaco, monospace; $editor-font-size: 16px; -$default-block-margin: 28px; // This value provides a consistent, contiguous spacing between blocks (it's 2x $block-padding). +$default-block-margin: 28px; // This value provides a consistent, contiguous spacing between blocks. $text-editor-font-size: 15px; $editor-line-height: 1.8; -$big-font-size: 18px; -$mobile-text-min-font-size: 16px; // Any font size below 16px will cause Mobile Safari to "zoom in" -$border-width: 1px; -$border-width-focus: 1.5px; -$border-width-tab: 4px; -$helptext-font-size: 12px; +$mobile-text-min-font-size: 16px; // Any font size below 16px will cause Mobile Safari to "zoom in". + /** * Grid System. @@ -35,11 +38,13 @@ $grid-unit-40: 4 * $grid-unit; // 32px $grid-unit-50: 5 * $grid-unit; // 40px $grid-unit-60: 6 * $grid-unit; // 48px + /** * Dimensions. */ $icon-size: 24px; +$button-margin: 0.5em; $button-size: 36px; $button-size-small: 24px; $header-height: 60px; @@ -52,11 +57,7 @@ $admin-sidebar-width-big: 190px; $admin-sidebar-width-collapsed: 36px; $modal-min-width: 360px; $spinner-size: 18px; -$mobile-header-toolbar-height: 44px; -$mobile-floating-toolbar-height: 44px; -$mobile-floating-toolbar-margin: 8px; -$mobile-color-swatch: 48px; -$header-toolbar-min-width: 335px; + /** * Shadows. @@ -71,21 +72,52 @@ $shadow-modal: 0 3px 30px rgba($black, 0.2); */ $sidebar-width: 280px; -$content-width: 580px; // This is optimized for 70 characters. +$content-width: 840px; +$wide-content-width: 1100px; $widget-area-width: 700px; + /** - * Block UI. + * Block & Editor UI. */ $block-toolbar-height: $grid-unit-60; +$border-width: 1px; +$border-width-focus: 1.5px; +$border-width-tab: 4px; +$helptext-font-size: 12px; +$radius-round: 50%; +$radius-block-ui: 2px; +$radio-input-size: 20px; +$radio-input-size-sm: 24px; // Width & height for small viewports. + +// Deprecated, please avoid using these. +$block-padding: 14px; // Used to define space between block footprint and surrouding borders. + + +/** + * Block paddings. + */ + +// Padding for blocks with a background color (e.g. paragraph or group). +$block-bg-padding--v: 1.25em; +$block-bg-padding--h: 2.375em; + + +/** + * React Native specific. + * These variables do not appear to be used anywhere else. + */ + +// Dimensions. +$mobile-header-toolbar-height: 44px; +$mobile-floating-toolbar-height: 44px; +$mobile-floating-toolbar-margin: 8px; +$mobile-color-swatch: 48px; + +// Block UI. $mobile-block-toolbar-height: 44px; -$block-padding: 14px; // Space between block footprint and focus boundaries. These are drawn outside the block footprint, and do not affect the size. -$block-spacing: 4px; // Vertical space between blocks. -$block-side-ui-width: $button-size; // Width of the movers/drag handle UI. -$block-side-ui-clearance: 2px; // Space between movers/drag handle UI, and block. $dimmed-opacity: 1; - $block-edge-to-content: 16px; $solid-border-space: 12px; $dashed-border-space: 6px; @@ -94,17 +126,3 @@ $block-selected-border-width: 1px; $block-selected-padding: 0; $block-selected-child-margin: 5px; $block-selected-to-content: $block-edge-to-content - $block-selected-margin - $block-selected-border-width; - -/** - * Border radii. - */ - -$radius-round: 50%; -$radius-block-ui: 2px; - -/** - * Block paddings. - */ -// Padding for blocks with a background color (e.g. paragraph or group). -$block-bg-padding--v: 1.25em; -$block-bg-padding--h: 2.375em; diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index 8fa08539160ac9..14c40bdeebd860 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -93,6 +93,9 @@ $z-layers: ( // Show the navigation toggle above the skeleton header ".edit-site-navigation-toggle": 31, + // Show the FSE template previews above the editor (same z-index as the skeleton header) + ".edit-site-navigation-panel__preview": 30, + // Show notices below expanded editor bar // .edit-post-header { z-index: 30 } ".components-notice-list": 29, diff --git a/packages/blob/package.json b/packages/blob/package.json index 65fc026c6fb758..c001f430b1b306 100644 --- a/packages/blob/package.json +++ b/packages/blob/package.json @@ -24,7 +24,7 @@ "types": "build-types", "sideEffects": false, "dependencies": { - "@babel/runtime": "^7.11.2" + "@babel/runtime": "^7.12.5" }, "publishConfig": { "access": "public" diff --git a/packages/block-directory/CHANGELOG.md b/packages/block-directory/CHANGELOG.md index 1d82266e5dbc4f..cf32748eb92eee 100644 --- a/packages/block-directory/CHANGELOG.md +++ b/packages/block-directory/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### New Feature + +- Added a store definition `store` for the block directory namespace to use with `@wordpress/data` API ([#26655](https://github.com/WordPress/gutenberg/pull/26655)). + ## 1.0.0 (2019-09-16) - Initial release. diff --git a/packages/block-directory/package.json b/packages/block-directory/package.json index 06d00e375e5385..0e3c453b7d7373 100644 --- a/packages/block-directory/package.json +++ b/packages/block-directory/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-directory", - "version": "1.17.4", + "version": "1.17.5", "description": "Extend editor with block directory features to search, download and install blocks.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -21,11 +21,8 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", - "sideEffects": [ - "build-style/**", - "!((src|build|build-module)/components/**)" - ], "dependencies": { + "@babel/runtime": "^7.12.5", "@wordpress/a11y": "file:../a11y", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/block-editor": "file:../block-editor", diff --git a/packages/block-directory/src/components/auto-block-uninstaller/index.js b/packages/block-directory/src/components/auto-block-uninstaller/index.js index 12016417bc8851..e35141c5e08581 100644 --- a/packages/block-directory/src/components/auto-block-uninstaller/index.js +++ b/packages/block-directory/src/components/auto-block-uninstaller/index.js @@ -5,8 +5,13 @@ import { unregisterBlockType } from '@wordpress/blocks'; import { useDispatch, useSelect } from '@wordpress/data'; import { useEffect } from '@wordpress/element'; +/** + * Internal dependencies + */ +import { store as blockDirectoryStore } from '../../store'; + export default function AutoBlockUninstaller() { - const { uninstallBlockType } = useDispatch( 'core/block-directory' ); + const { uninstallBlockType } = useDispatch( blockDirectoryStore ); const shouldRemoveBlockTypes = useSelect( ( select ) => { const { isAutosavingPost, isSavingPost } = select( 'core/editor' ); @@ -14,7 +19,7 @@ export default function AutoBlockUninstaller() { }, [] ); const unusedBlockTypes = useSelect( - ( select ) => select( 'core/block-directory' ).getUnusedBlockTypes(), + ( select ) => select( blockDirectoryStore ).getUnusedBlockTypes(), [] ); diff --git a/packages/block-directory/src/components/downloadable-block-list-item/index.js b/packages/block-directory/src/components/downloadable-block-list-item/index.js index 5d35741af51384..8a88e8593f0adc 100644 --- a/packages/block-directory/src/components/downloadable-block-list-item/index.js +++ b/packages/block-directory/src/components/downloadable-block-list-item/index.js @@ -10,12 +10,13 @@ import DownloadableBlockAuthorInfo from '../downloadable-block-author-info'; import DownloadableBlockHeader from '../downloadable-block-header'; import DownloadableBlockInfo from '../downloadable-block-info'; import DownloadableBlockNotice from '../downloadable-block-notice'; +import { store as blockDirectoryStore } from '../../store'; export default function DownloadableBlockListItem( { item, onClick } ) { const { isLoading, isInstallable } = useSelect( ( select ) => { const { isInstalling, getErrorNoticeForBlock } = select( - 'core/block-directory' + blockDirectoryStore ); const notice = getErrorNoticeForBlock( item.id ); const hasFatal = notice && notice.isFatal; diff --git a/packages/block-directory/src/components/downloadable-block-notice/index.js b/packages/block-directory/src/components/downloadable-block-notice/index.js index 75e7d5a513bca9..f24545954e63ae 100644 --- a/packages/block-directory/src/components/downloadable-block-notice/index.js +++ b/packages/block-directory/src/components/downloadable-block-notice/index.js @@ -5,10 +5,15 @@ import { __ } from '@wordpress/i18n'; import { Button, Notice } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { store as blockDirectoryStore } from '../../store'; + export const DownloadableBlockNotice = ( { block, onClick } ) => { const errorNotice = useSelect( ( select ) => - select( 'core/block-directory' ).getErrorNoticeForBlock( block.id ), + select( blockDirectoryStore ).getErrorNoticeForBlock( block.id ), [ block ] ); diff --git a/packages/block-directory/src/components/downloadable-blocks-list/index.js b/packages/block-directory/src/components/downloadable-blocks-list/index.js index 5aff92c1914a15..d732b7bc800d5f 100644 --- a/packages/block-directory/src/components/downloadable-blocks-list/index.js +++ b/packages/block-directory/src/components/downloadable-blocks-list/index.js @@ -12,9 +12,10 @@ import { useDispatch } from '@wordpress/data'; * Internal dependencies */ import DownloadableBlockListItem from '../downloadable-block-list-item'; +import { store as blockDirectoryStore } from '../../store'; function DownloadableBlocksList( { items, onHover = noop, onSelect } ) { - const { installBlockType } = useDispatch( 'core/block-directory' ); + const { installBlockType } = useDispatch( blockDirectoryStore ); const { setIsInserterOpened } = useDispatch( 'core/edit-post' ); if ( ! items.length ) { diff --git a/packages/block-directory/src/components/downloadable-blocks-panel/index.js b/packages/block-directory/src/components/downloadable-blocks-panel/index.js index 6cdde9efc28c9a..fa6af8b174dbda 100644 --- a/packages/block-directory/src/components/downloadable-blocks-panel/index.js +++ b/packages/block-directory/src/components/downloadable-blocks-panel/index.js @@ -12,6 +12,7 @@ import { speak } from '@wordpress/a11y'; * Internal dependencies */ import DownloadableBlocksList from '../downloadable-blocks-list'; +import { store as blockDirectoryStore } from '../../store'; function DownloadableBlocksPanel( { downloadableItems, @@ -80,7 +81,7 @@ export default compose( [ const { getDownloadableBlocks, isRequestingDownloadableBlocks, - } = select( 'core/block-directory' ); + } = select( blockDirectoryStore ); const hasPermission = select( 'core' ).canUser( 'read', diff --git a/packages/block-directory/src/index.js b/packages/block-directory/src/index.js index 1c5eccc183bc29..2623b6b2b7284a 100644 --- a/packages/block-directory/src/index.js +++ b/packages/block-directory/src/index.js @@ -1,10 +1,6 @@ -/** - * WordPress dependencies - */ -import '@wordpress/notices'; - /** * Internal dependencies */ -import './store'; import './plugins'; + +export { store } from './store'; diff --git a/packages/block-directory/src/plugins/get-install-missing/index.js b/packages/block-directory/src/plugins/get-install-missing/index.js index da4337e69249c0..563fd6e845b582 100644 --- a/packages/block-directory/src/plugins/get-install-missing/index.js +++ b/packages/block-directory/src/plugins/get-install-missing/index.js @@ -12,6 +12,7 @@ import { Warning } from '@wordpress/block-editor'; * Internal dependencies */ import InstallButton from './install-button'; +import { store as blockDirectoryStore } from '../../store'; const getInstallMissing = ( OriginalComponent ) => ( props ) => { const { originalName, originalUndelimitedContent } = props.attributes; @@ -19,7 +20,7 @@ const getInstallMissing = ( OriginalComponent ) => ( props ) => { // eslint-disable-next-line react-hooks/rules-of-hooks const { block, hasPermission } = useSelect( ( select ) => { - const { getDownloadableBlocks } = select( 'core/block-directory' ); + const { getDownloadableBlocks } = select( blockDirectoryStore ); const blocks = getDownloadableBlocks( 'block:' + originalName ).filter( ( { name } ) => originalName === name ); diff --git a/packages/block-directory/src/plugins/get-install-missing/install-button.js b/packages/block-directory/src/plugins/get-install-missing/install-button.js index f4985de41846e2..7805589da40490 100644 --- a/packages/block-directory/src/plugins/get-install-missing/install-button.js +++ b/packages/block-directory/src/plugins/get-install-missing/install-button.js @@ -6,11 +6,16 @@ import { Button } from '@wordpress/components'; import { createBlock, getBlockType, parse } from '@wordpress/blocks'; import { useSelect, useDispatch } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { store as blockDirectoryStore } from '../../store'; + export default function InstallButton( { attributes, block, clientId } ) { const isInstallingBlock = useSelect( ( select ) => - select( 'core/block-directory' ).isInstalling( block.id ) + select( blockDirectoryStore ).isInstalling( block.id ) ); - const { installBlockType } = useDispatch( 'core/block-directory' ); + const { installBlockType } = useDispatch( blockDirectoryStore ); const { replaceBlock } = useDispatch( 'core/block-editor' ); return ( diff --git a/packages/block-directory/src/plugins/installed-blocks-pre-publish-panel/index.js b/packages/block-directory/src/plugins/installed-blocks-pre-publish-panel/index.js index f00528793dc634..a51b68fcaffef2 100644 --- a/packages/block-directory/src/plugins/installed-blocks-pre-publish-panel/index.js +++ b/packages/block-directory/src/plugins/installed-blocks-pre-publish-panel/index.js @@ -10,10 +10,11 @@ import { blockDefault } from '@wordpress/icons'; * Internal dependencies */ import CompactList from '../../components/compact-list'; +import { store as blockDirectoryStore } from '../../store'; export default function InstalledBlocksPrePublishPanel() { const newBlockTypes = useSelect( - ( select ) => select( 'core/block-directory' ).getNewBlockTypes(), + ( select ) => select( blockDirectoryStore ).getNewBlockTypes(), [] ); diff --git a/packages/block-directory/src/store/actions.js b/packages/block-directory/src/store/actions.js index 905c46590a654a..ee143492ab7b56 100644 --- a/packages/block-directory/src/store/actions.js +++ b/packages/block-directory/src/store/actions.js @@ -1,8 +1,11 @@ /** * WordPress dependencies */ +import { store as blocksStore } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; -import { apiFetch, dispatch, select } from '@wordpress/data-controls'; +import { controls } from '@wordpress/data'; +import { apiFetch } from '@wordpress/data-controls'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies @@ -83,11 +86,11 @@ export function* installBlockType( block ) { } ); yield loadAssets( assets ); - const registeredBlocks = yield select( 'core/blocks', 'getBlockTypes' ); - if ( - ! registeredBlocks.length || - ! registeredBlocks.filter( ( i ) => i.name === block.name ).length - ) { + const registeredBlocks = yield controls.select( + blocksStore.name, + 'getBlockTypes' + ); + if ( ! registeredBlocks.some( ( i ) => i.name === block.name ) ) { throw new Error( __( 'Error registering block. Try reloading the page.' ) ); @@ -141,8 +144,8 @@ export function* uninstallBlockType( block ) { } ); yield removeInstalledBlockType( block ); } catch ( error ) { - yield dispatch( - 'core/notices', + yield controls.dispatch( + noticesStore, 'createErrorNotice', error.message || __( 'An error occurred.' ) ); diff --git a/packages/block-directory/src/store/controls.js b/packages/block-directory/src/store/controls.js index a5ee96d8e6c9d4..e5b983642dbf59 100644 --- a/packages/block-directory/src/store/controls.js +++ b/packages/block-directory/src/store/controls.js @@ -7,7 +7,7 @@ import apiFetch from '@wordpress/api-fetch'; * Load an asset for a block. * * This function returns a Promise that will resolve once the asset is loaded, - * or in the case of Stylesheets and Inline Javascript, will resolve immediately. + * or in the case of Stylesheets and Inline JavaScript, will resolve immediately. * * @param {HTMLElement} el A HTML Element asset to inject. * @@ -65,10 +65,10 @@ const controls = { LOAD_ASSETS() { /* * Fetch the current URL (post-new.php, or post.php?post=1&action=edit) and compare the - * Javascript and CSS assets loaded between the pages. This imports the required assets + * JavaScript and CSS assets loaded between the pages. This imports the required assets * for the block into the current page while not requiring that we know them up-front. * In the future this can be improved by reliance upon block.json and/or a script-loader - * dependancy API. + * dependency API. */ return apiFetch( { url: document.location.href, diff --git a/packages/block-directory/src/store/index.js b/packages/block-directory/src/store/index.js index 8e81d70023084c..ca3f2c8b939dd6 100644 --- a/packages/block-directory/src/store/index.js +++ b/packages/block-directory/src/store/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { registerStore } from '@wordpress/data'; +import { createReduxStore, register } from '@wordpress/data'; import { controls as dataControls } from '@wordpress/data-controls'; /** @@ -16,7 +16,7 @@ import controls from './controls'; /** * Module Constants */ -const MODULE_KEY = 'core/block-directory'; +const STORE_NAME = 'core/block-directory'; /** * Block editor data store configuration. @@ -33,6 +33,13 @@ export const storeConfig = { resolvers, }; -const store = registerStore( MODULE_KEY, storeConfig ); +/** + * Store definition for the block directory namespace. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore + * + * @type {Object} + */ +export const store = createReduxStore( STORE_NAME, storeConfig ); -export default store; +register( store ); diff --git a/packages/block-directory/src/store/test/actions.js b/packages/block-directory/src/store/test/actions.js index 516f6deef3fe1d..0b40494b44d838 100644 --- a/packages/block-directory/src/store/test/actions.js +++ b/packages/block-directory/src/store/test/actions.js @@ -1,3 +1,9 @@ +/** + * WordPress dependencies + */ +import { store as blocksStore } from '@wordpress/blocks'; +import { store as noticesStore } from '@wordpress/notices'; + /** * Internal dependencies */ @@ -80,8 +86,8 @@ describe( 'actions', () => { expect( generator.next().value ).toEqual( { args: [], selectorName: 'getBlockTypes', - storeKey: 'core/blocks', - type: '@@data/RESOLVE_SELECT', + storeKey: blocksStore.name, + type: '@@data/SELECT', } ); expect( generator.next( [ block ] ).value ).toEqual( { @@ -142,8 +148,8 @@ describe( 'actions', () => { expect( generator.next().value ).toEqual( { args: [], selectorName: 'getBlockTypes', - storeKey: 'core/blocks', - type: '@@data/RESOLVE_SELECT', + storeKey: blocksStore.name, + type: '@@data/SELECT', } ); expect( generator.next( [ inactiveBlock ] ).value ).toEqual( { @@ -276,7 +282,7 @@ describe( 'actions', () => { expect( generator.throw( apiError ).value ).toMatchObject( { type: '@@data/DISPATCH', actionName: 'createErrorNotice', - storeKey: 'core/notices', + storeKey: noticesStore, } ); expect( generator.next() ).toEqual( { diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index 7179c21f6cb6fb..76cd8209c15b0c 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -2,12 +2,15 @@ ## Unreleased +### New Feature + +- Added a store definition `store` for the block editor namespace to use with `@wordpress/data` API ([#26655](https://github.com/WordPress/gutenberg/pull/26655)). + ## 5.0.0 (2020-10-06) ### Breaking Changes -- The block editor does not contain default colors, gradients, and font sizes anymore. If one wants to take advantage of these features, please explicitly pass colors, gradients, and/or settings or use the new __experimentalFeatures setting that is available. - +- The block editor does not contain default colors, gradients, and font sizes anymore. If one wants to take advantage of these features, please explicitly pass colors, gradients, and/or settings or use the new \_\_experimentalFeatures setting that is available. ## 4.0.0 (2020-05-28) diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 97e85571ee7589..cfadf407b3b655 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -497,8 +497,6 @@ _Properties_ - _codeEditingEnabled_ `boolean`: Whether or not the user can switch to the code editor - _\_\_experimentalCanUserUseUnfilteredHTML_ `boolean`: Whether the user should be able to use unfiltered HTML or the HTML should be filtered e.g., to remove elements considered insecure like iframes. - _\_\_experimentalBlockDirectory_ `boolean`: Whether the user has enabled the Block Directory -- _\_\_experimentalEnableFullSiteEditing_ `boolean`: Whether the user has enabled Full Site Editing -- _\_\_experimentalEnableFullSiteEditingDemo_ `boolean`: Whether the user has enabled Full Site Editing Demo Templates - _\_\_experimentalBlockPatterns_ `Array`: Array of objects representing the block patterns - _\_\_experimentalBlockPatternCategories_ `Array`: Array of objects representing the block pattern categories @@ -506,6 +504,18 @@ _Properties_ Undocumented declaration. +# **store** + +Store definition for the block editor namespace. + +_Related_ + +- + +_Type_ + +- `Object` + # **storeConfig** Block editor data store configuration. diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index aefd15e439c29e..bd98e24d88e2c3 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-editor", - "version": "5.1.3", + "version": "5.1.4", "description": "Generic block editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -24,16 +24,18 @@ "react-native": "src/index", "sideEffects": [ "build-style/**", - "!((src|build|build-module)/(components|utils)/**)" + "src/**/*.scss", + "{src,build,build-module}/{index.js,store/index.js,hooks/**}" ], "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/a11y": "file:../a11y", "@wordpress/blob": "file:../blob", "@wordpress/blocks": "file:../blocks", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", + "@wordpress/data-controls": "file:../data-controls", "@wordpress/deprecated": "file:../deprecated", "@wordpress/dom": "file:../dom", "@wordpress/element": "file:../element", @@ -49,8 +51,6 @@ "@wordpress/shortcode": "file:../shortcode", "@wordpress/token-list": "file:../token-list", "@wordpress/url": "file:../url", - "@wordpress/viewport": "file:../viewport", - "@wordpress/warning": "file:../warning", "@wordpress/wordcount": "file:../wordcount", "classnames": "^2.2.5", "css-mediaquery": "^0.1.2", @@ -59,12 +59,10 @@ "inherits": "^2.0.3", "lodash": "^4.17.19", "memize": "^1.1.0", - "react-autosize-textarea": "^3.0.2", + "react-autosize-textarea": "^7.1.0", "react-spring": "^8.0.19", - "react-transition-group": "^2.9.0", "reakit": "1.1.0", "redux-multi": "^0.1.12", - "refx": "^3.0.0", "rememo": "^3.0.0", "tinycolor2": "^1.4.1", "traverse": "^0.6.6" diff --git a/packages/block-editor/src/autocompleters/style.scss b/packages/block-editor/src/autocompleters/style.scss index fc51b518435e50..294417aee27400 100644 --- a/packages/block-editor/src/autocompleters/style.scss +++ b/packages/block-editor/src/autocompleters/style.scss @@ -1,5 +1,7 @@ .block-editor-autocompleters__block { + white-space: nowrap; + .block-editor-block-icon { - margin-right: 8px; + margin-right: $grid-unit-10; } } diff --git a/packages/block-editor/src/components/block-actions/index.js b/packages/block-editor/src/components/block-actions/index.js index e7de0fa5c40f32..da858d2b9a7e39 100644 --- a/packages/block-editor/src/components/block-actions/index.js +++ b/packages/block-editor/src/components/block-actions/index.js @@ -7,7 +7,11 @@ import { castArray, first, last, every } from 'lodash'; * WordPress dependencies */ import { useDispatch, useSelect } from '@wordpress/data'; -import { hasBlockSupport, switchToBlockType } from '@wordpress/blocks'; +import { + hasBlockSupport, + switchToBlockType, + store as blocksStore, +} from '@wordpress/blocks'; /** * Internal dependencies @@ -26,7 +30,7 @@ export default function BlockActions( { getTemplateLock, } = useSelect( ( select ) => select( 'core/block-editor' ), [] ); const { getDefaultBlockName, getGroupingBlockName } = useSelect( - ( select ) => select( 'core/blocks' ), + ( select ) => select( blocksStore ), [] ); diff --git a/packages/block-editor/src/components/block-alignment-toolbar/index.js b/packages/block-editor/src/components/block-alignment-toolbar/index.js index 2e4ac8ea7556db..769e96d4542198 100644 --- a/packages/block-editor/src/components/block-alignment-toolbar/index.js +++ b/packages/block-editor/src/components/block-alignment-toolbar/index.js @@ -3,8 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { ToolbarGroup } from '@wordpress/components'; -import { withSelect } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; +import { useSelect } from '@wordpress/data'; import { positionCenter, positionLeft, @@ -13,6 +12,11 @@ import { stretchWide, } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import { useLayout } from '../inner-blocks/layout'; + const BLOCK_ALIGNMENTS_CONTROLS = { left: { icon: positionLeft, @@ -49,18 +53,36 @@ export function BlockAlignmentToolbar( { onChange, controls = DEFAULT_CONTROLS, isCollapsed = true, - wideControlsEnabled = false, } ) { + const { wideControlsEnabled = false } = useSelect( ( select ) => { + const { getSettings } = select( 'core/block-editor' ); + const settings = getSettings(); + return { + wideControlsEnabled: settings.alignWide, + }; + } ); + const layout = useLayout(); + const supportsAlignments = layout.type === 'default'; + + if ( ! supportsAlignments ) { + return null; + } + + const { alignments: availableAlignments = DEFAULT_CONTROLS } = layout; + const enabledControls = controls.filter( + ( control ) => + ( wideControlsEnabled || ! WIDE_CONTROLS.includes( control ) ) && + availableAlignments.includes( control ) + ); + + if ( enabledControls.length === 0 ) { + return null; + } + function applyOrUnset( align ) { return () => onChange( value === align ? undefined : align ); } - const enabledControls = wideControlsEnabled - ? controls - : controls.filter( - ( control ) => WIDE_CONTROLS.indexOf( control ) === -1 - ); - const activeAlignmentControl = BLOCK_ALIGNMENTS_CONTROLS[ value ]; const defaultAlignmentControl = BLOCK_ALIGNMENTS_CONTROLS[ DEFAULT_CONTROL ]; @@ -87,12 +109,4 @@ export function BlockAlignmentToolbar( { ); } -export default compose( - withSelect( ( select ) => { - const { getSettings } = select( 'core/block-editor' ); - const settings = getSettings(); - return { - wideControlsEnabled: settings.alignWide, - }; - } ) -)( BlockAlignmentToolbar ); +export default BlockAlignmentToolbar; diff --git a/packages/block-editor/src/components/block-alignment-toolbar/test/index.js b/packages/block-editor/src/components/block-alignment-toolbar/test/index.js index 976aa7d4abd896..98d6f31d25f6a9 100644 --- a/packages/block-editor/src/components/block-alignment-toolbar/test/index.js +++ b/packages/block-editor/src/components/block-alignment-toolbar/test/index.js @@ -3,11 +3,23 @@ */ import { shallow } from 'enzyme'; +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; + /** * Internal dependencies */ import { BlockAlignmentToolbar } from '../'; +jest.mock( '@wordpress/data/src/components/use-select', () => { + // This allows us to tweak the returned value on each test + const mock = jest.fn(); + return mock; +} ); +useSelect.mockImplementation( () => ( { wideControlsEnabled: false } ) ); + describe( 'BlockAlignmentToolbar', () => { const alignment = 'left'; const onChange = jest.fn(); diff --git a/packages/block-editor/src/components/block-card/index.js b/packages/block-editor/src/components/block-card/index.js index aaff1af27e16dd..c209ea39b5c73e 100644 --- a/packages/block-editor/src/components/block-card/index.js +++ b/packages/block-editor/src/components/block-card/index.js @@ -3,16 +3,14 @@ */ import BlockIcon from '../block-icon'; -function BlockCard( { blockType } ) { +function BlockCard( { blockType: { icon, title, description } } ) { return (
- +
-

- { blockType.title } -

+

{ title }

- { blockType.description } + { description }
diff --git a/packages/block-editor/src/components/block-compare/style.scss b/packages/block-editor/src/components/block-compare/style.scss index 90301874000653..5474ad6a264162 100644 --- a/packages/block-editor/src/components/block-compare/style.scss +++ b/packages/block-editor/src/components/block-compare/style.scss @@ -60,7 +60,7 @@ .block-editor-block-compare__preview { padding: 0; - padding-top: $block-padding; + padding-top: $grid-unit-20; p { font-size: 12px; @@ -69,7 +69,7 @@ } .block-editor-block-compare__action { - margin-top: $block-padding; + margin-top: $grid-unit-20; } .block-editor-block-compare__heading { diff --git a/packages/block-editor/src/components/block-controls/index.js b/packages/block-editor/src/components/block-controls/index.js index f2b35e4b5301f8..297d3839df4aa9 100644 --- a/packages/block-editor/src/components/block-controls/index.js +++ b/packages/block-editor/src/components/block-controls/index.js @@ -20,24 +20,18 @@ import useDisplayBlockControls from '../use-display-block-controls'; const { Fill, Slot } = createSlotFill( 'BlockControls' ); -function BlockControlsSlot( { __experimentalIsExpanded = false, ...props } ) { +function BlockControlsSlot( props ) { const accessibleToolbarState = useContext( ToolbarContext ); - return ( - - ); + return ; } -function BlockControlsFill( { controls, __experimentalIsExpanded, children } ) { +function BlockControlsFill( { controls, children } ) { if ( ! useDisplayBlockControls() ) { return null; } return ( - + { ( fillProps ) => { // Children passed to BlockControlsFill will not have access to any // React Context whose Provider is part of the BlockControlsSlot tree. @@ -54,9 +48,6 @@ function BlockControlsFill( { controls, __experimentalIsExpanded, children } ) { ); } -const buildSlotName = ( isExpanded ) => - `BlockControls${ isExpanded ? '-expanded' : '' }`; - const BlockControls = BlockControlsFill; BlockControls.Slot = BlockControlsSlot; diff --git a/packages/block-editor/src/components/block-draggable/draggable-chip.js b/packages/block-editor/src/components/block-draggable/draggable-chip.js index 2849645ab76de7..5ba2b4b46da3a0 100644 --- a/packages/block-editor/src/components/block-draggable/draggable-chip.js +++ b/packages/block-editor/src/components/block-draggable/draggable-chip.js @@ -2,8 +2,6 @@ * WordPress dependencies */ import { _n, sprintf } from '@wordpress/i18n'; -import { getBlockType } from '@wordpress/blocks'; -import { useSelect } from '@wordpress/data'; import { Flex, FlexItem } from '@wordpress/components'; import { dragHandle } from '@wordpress/icons'; @@ -12,22 +10,7 @@ import { dragHandle } from '@wordpress/icons'; */ import BlockIcon from '../block-icon'; -export default function BlockDraggableChip( { clientIds } ) { - const icon = useSelect( - ( select ) => { - if ( clientIds.length !== 1 ) { - return; - } - - const { getBlockName } = select( 'core/block-editor' ); - const [ firstId ] = clientIds; - const blockName = getBlockName( firstId ); - - return getBlockType( blockName )?.icon; - }, - [ clientIds ] - ); - +export default function BlockDraggableChip( { count, icon } ) { return (
@@ -41,8 +24,8 @@ export default function BlockDraggableChip( { clientIds } ) { ) : ( sprintf( /* translators: %d: Number of blocks. */ - _n( '%d block', '%d blocks', clientIds.length ), - clientIds.length + _n( '%d block', '%d blocks', count ), + count ) ) } diff --git a/packages/block-editor/src/components/block-draggable/index.js b/packages/block-editor/src/components/block-draggable/index.js index 238a86c8937ea7..bb7692ec2abf9f 100644 --- a/packages/block-editor/src/components/block-draggable/index.js +++ b/packages/block-editor/src/components/block-draggable/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { getBlockType } from '@wordpress/blocks'; import { Draggable } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { useEffect, useRef } from '@wordpress/element'; @@ -19,19 +20,23 @@ const BlockDraggable = ( { onDragEnd, elementId, } ) => { - const { srcRootClientId, isDraggable } = useSelect( + const { srcRootClientId, isDraggable, icon } = useSelect( ( select ) => { - const { getBlockRootClientId, getTemplateLock } = select( - 'core/block-editor' - ); + const { + getBlockRootClientId, + getTemplateLock, + getBlockName, + } = select( 'core/block-editor' ); const rootClientId = getBlockRootClientId( clientIds[ 0 ] ); const templateLock = rootClientId ? getTemplateLock( rootClientId ) : null; + const blockName = getBlockName( clientIds[ 0 ] ); return { srcRootClientId: rootClientId, isDraggable: 'all' !== templateLock, + icon: getBlockType( blockName )?.icon, }; }, [ clientIds ] @@ -69,7 +74,7 @@ const BlockDraggable = ( { return ( { startDraggingBlocks( clientIds ); @@ -93,14 +98,14 @@ const BlockDraggable = ( { } } } __experimentalDragComponent={ - + } > { ( { onDraggableStart, onDraggableEnd } ) => { return children( { - isDraggable: true, - onDraggableStart, - onDraggableEnd, + draggable: true, + onDragStart: onDraggableStart, + onDragEnd: onDraggableEnd, } ); } } diff --git a/packages/block-editor/src/components/block-draggable/stories/index.js b/packages/block-editor/src/components/block-draggable/stories/index.js index 1389511a382006..79255bebcda4e1 100644 --- a/packages/block-editor/src/components/block-draggable/stories/index.js +++ b/packages/block-editor/src/components/block-draggable/stories/index.js @@ -1,15 +1,16 @@ -/** - * WordPress dependencies - */ -import { wordpress } from '@wordpress/icons'; - /** * Internal dependencies */ -import { BlockDraggableChip } from '../draggable-chip'; +import BlockDraggableChip from '../draggable-chip'; export default { title: 'BlockEditor/BlockDraggable' }; export const _default = () => { - return ; + // create a wrapper box for the absolutely-positioned child component + const wrapperStyle = { margin: '24px 0', position: 'relative' }; + return ( +
+ +
+ ); }; diff --git a/packages/block-editor/src/components/block-full-height-alignment-toolbar/README.md b/packages/block-editor/src/components/block-full-height-alignment-toolbar/README.md new file mode 100644 index 00000000000000..6febd1fc272f3a --- /dev/null +++ b/packages/block-editor/src/components/block-full-height-alignment-toolbar/README.md @@ -0,0 +1,3 @@ +# Full Height Toolbar Toolbar + +Unlike the block alignment options, `Full Height Alignment` can be applied to a block in an independent way. But also, it works very well together with these block alignment options, where the combination empowers the design-layout capability. \ No newline at end of file diff --git a/packages/block-editor/src/components/block-full-height-alignment-toolbar/index.js b/packages/block-editor/src/components/block-full-height-alignment-toolbar/index.js new file mode 100644 index 00000000000000..0912a5b8fb26b6 --- /dev/null +++ b/packages/block-editor/src/components/block-full-height-alignment-toolbar/index.js @@ -0,0 +1,25 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { ToolbarGroup, ToolbarButton } from '@wordpress/components'; +import { fullscreen } from '@wordpress/icons'; + +function BlockFullHeightAlignmentToolbar( { + isActive, + label = __( 'Toggle full height' ), + onToggle, +} ) { + return ( + + onToggle( ! isActive ) } + /> + + ); +} + +export default BlockFullHeightAlignmentToolbar; diff --git a/packages/block-editor/src/components/block-icon/README.md b/packages/block-editor/src/components/block-icon/README.md index cc366e7afad72d..51aed14b340215 100644 --- a/packages/block-editor/src/components/block-icon/README.md +++ b/packages/block-editor/src/components/block-icon/README.md @@ -1,6 +1,6 @@ # Block Icon -The BlockIcon component provides an icon for blocks in different places in the editor: the toolbar of a selected block, the placeholders of certain blocks (gallery, image), the block insertion panel of the editor or the left sidebar. +The BlockIcon component provides an icon for blocks in different places in the editor: the toolbar of a selected block, the placeholders of certain blocks (gallery, image), the block insertion panel of the editor or the secondary sidebar. The rendered an [Icon](https://github.com/WordPress/gutenberg/tree/master/packages/components/src/icon) component with default styles. @@ -26,4 +26,4 @@ const MyBlockIcon = () => ## Related components -Block Editor components are components that can be used to compose the UI of your block editor. Thus, they can only be used under a [`BlockEditorProvider`](https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/provider/README.md) in the components tree. +Block Editor components are components that can be used to compose the UI of your block editor. Thus, they can only be used under a [`BlockEditorProvider`](https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/provider/README.md) in the components tree. diff --git a/packages/block-editor/src/components/block-icon/index.native.js b/packages/block-editor/src/components/block-icon/index.native.js index f0845e6b74f8ad..3e1bca650976a2 100644 --- a/packages/block-editor/src/components/block-icon/index.native.js +++ b/packages/block-editor/src/components/block-icon/index.native.js @@ -8,15 +8,33 @@ import { View } from 'react-native'; */ import { Icon } from '@wordpress/components'; import { blockDefault } from '@wordpress/icons'; +import { withPreferredColorScheme } from '@wordpress/compose'; -export default function BlockIcon( { icon, showColors = false } ) { +/** + * Internal dependencies + */ +import styles from './style.scss'; + +export function BlockIcon( { + icon, + showColors = false, + getStylesFromColorScheme, +} ) { if ( icon?.src === 'block-default' ) { icon = { src: blockDefault, }; } - const renderedIcon = ; + const renderedIcon = ( + + ); const style = showColors ? { backgroundColor: icon && icon.background, @@ -26,3 +44,5 @@ export default function BlockIcon( { icon, showColors = false } ) { return { renderedIcon }; } + +export default withPreferredColorScheme( BlockIcon ); diff --git a/packages/block-editor/src/components/block-icon/style.native.scss b/packages/block-editor/src/components/block-icon/style.native.scss new file mode 100644 index 00000000000000..605312709ef5c9 --- /dev/null +++ b/packages/block-editor/src/components/block-icon/style.native.scss @@ -0,0 +1,7 @@ +.iconPlaceholder { + fill: $gray-dark; +} + +.iconPlaceholderDark { + fill: $white; +} diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index 259f4742febcd1..be2433edbe39da 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -6,6 +6,7 @@ import { getBlockType, getUnregisteredTypeHandlerName, hasBlockSupport, + store as blocksStore, } from '@wordpress/blocks'; import { PanelBody, @@ -23,6 +24,7 @@ import InspectorAdvancedControls from '../inspector-advanced-controls'; import BlockStyles from '../block-styles'; import MultiSelectionInspector from '../multi-selection-inspector'; import DefaultStylePicker from '../default-style-picker'; +import BlockVariationTransforms from '../block-variation-transforms'; const BlockInspector = ( { blockType, count, @@ -66,6 +68,7 @@ const BlockInspector = ( { return (
+ { hasBlockStyles && (
@@ -119,7 +122,7 @@ export default withSelect( ( select ) => { getSelectedBlockCount, getBlockName, } = select( 'core/block-editor' ); - const { getBlockStyles } = select( 'core/blocks' ); + const { getBlockStyles } = select( blocksStore ); const selectedBlockClientId = getSelectedBlockClientId(); const selectedBlockName = selectedBlockClientId && getBlockName( selectedBlockClientId ); diff --git a/packages/block-editor/src/components/block-list-appender/index.js b/packages/block-editor/src/components/block-list-appender/index.js index fae056e50dcb50..0fc9b408ee8466 100644 --- a/packages/block-editor/src/components/block-list-appender/index.js +++ b/packages/block-editor/src/components/block-list-appender/index.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { createContext, useContext } from '@wordpress/element'; +import { createContext } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; import { getDefaultBlockName } from '@wordpress/blocks'; @@ -34,8 +34,6 @@ function BlockListAppender( { selectedBlockClientId, tagName: TagName = 'div', } ) { - const appenderNodesMap = useContext( AppenderNodesContext ); - if ( isLocked || CustomAppender === false ) { return null; } @@ -100,15 +98,6 @@ function BlockListAppender( { 'wp-block', className ) } - ref={ ( ref ) => { - if ( ref ) { - // Set the reference of the "Appender" with `rootClientId` as key. - appenderNodesMap.set( rootClientId || '', ref ); - } else { - // If it un-mounts, cleanup the map. - appenderNodesMap.delete( rootClientId || '' ); - } - } } > { appender } diff --git a/packages/block-editor/src/components/block-list-appender/style.scss b/packages/block-editor/src/components/block-list-appender/style.scss index f5eb38c57f8f47..1b893c26875d7e 100644 --- a/packages/block-editor/src/components/block-list-appender/style.scss +++ b/packages/block-editor/src/components/block-list-appender/style.scss @@ -3,12 +3,6 @@ .block-editor-block-list__block .block-list-appender { margin: $grid-unit-10 0; - // Add additional margin to the appender when inside a group with a background color. - // If changing this, be sure to sync up with group/editor.scss line 13. - .has-background & { - margin: ($grid-unit-20 + $block-spacing) $grid-unit-10; - } - // Animate appearance. .block-list-appender__toggle { padding: 0; diff --git a/packages/block-editor/src/components/block-list/block-contextual-toolbar.js b/packages/block-editor/src/components/block-list/block-contextual-toolbar.js index 154a8f2e1f303c..3d02a274795862 100644 --- a/packages/block-editor/src/components/block-list/block-contextual-toolbar.js +++ b/packages/block-editor/src/components/block-list/block-contextual-toolbar.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { hasBlockSupport } from '@wordpress/blocks'; +import { hasBlockSupport, store as blocksStore } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; /** @@ -16,7 +16,7 @@ function BlockContextualToolbar( { focusOnMount, ...props } ) { const { getBlockName, getSelectedBlockClientIds } = select( 'core/block-editor' ); - const { getBlockType } = select( 'core/blocks' ); + const { getBlockType } = select( blocksStore ); const selectedBlockClientIds = getSelectedBlockClientIds(); const selectedBlockClientId = selectedBlockClientIds[ 0 ]; return { diff --git a/packages/block-editor/src/components/block-list/block-popover.js b/packages/block-editor/src/components/block-list/block-popover.js index e2b5038c890f4b..5bf1e5a12164df 100644 --- a/packages/block-editor/src/components/block-list/block-popover.js +++ b/packages/block-editor/src/components/block-list/block-popover.js @@ -7,12 +7,19 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useState, useCallback, useContext } from '@wordpress/element'; +import { + useState, + useCallback, + useContext, + useRef, + useEffect, +} from '@wordpress/element'; import { isUnmodifiedDefaultBlock } from '@wordpress/blocks'; import { Popover } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; import { useShortcut } from '@wordpress/keyboard-shortcuts'; import { useViewportMatch } from '@wordpress/compose'; +import { getScrollContainer } from '@wordpress/dom'; /** * Internal dependencies @@ -20,7 +27,8 @@ import { useViewportMatch } from '@wordpress/compose'; import BlockSelectionButton from './block-selection-button'; import BlockContextualToolbar from './block-contextual-toolbar'; import Inserter from '../inserter'; -import { BlockNodes } from './root-container'; +import { BlockNodes } from './'; +import { getBlockDOMNode } from '../../utils/dom'; function selector( select ) { const { @@ -84,7 +92,9 @@ function BlockPopover( { useShortcut( 'core/block-editor/focus-toolbar', - useCallback( () => setIsToolbarForced( true ), [] ), + useCallback( () => { + setIsToolbarForced( true ); + }, [] ), { bindGlobal: true, eventName: 'keydown', @@ -92,6 +102,16 @@ function BlockPopover( { } ); + // Stores the active toolbar item index so the block toolbar can return focus + // to it when re-mounting. + const initialToolbarItemIndexRef = useRef(); + + useEffect( () => { + // Resets the index whenever the active block changes so this is not + // persisted. See https://github.com/WordPress/gutenberg/pull/25760#issuecomment-717906169 + initialToolbarItemIndexRef.current = undefined; + }, [ clientId ] ); + if ( ! shouldShowBreadcrumb && ! shouldShowContextualToolbar && @@ -103,14 +123,16 @@ function BlockPopover( { let node = blockNodes[ clientId ]; - if ( capturingClientId ) { - node = document.getElementById( 'block-' + capturingClientId ); - } - if ( ! node ) { return null; } + const { ownerDocument } = node; + + if ( capturingClientId ) { + node = getBlockDOMNode( capturingClientId, ownerDocument ); + } + let anchorRef = node; if ( hasMultiSelection ) { @@ -143,6 +165,9 @@ function BlockPopover( { const popoverPosition = showEmptyBlockSideInserter ? 'top left right' : 'top right left'; + const stickyBoundaryElement = showEmptyBlockSideInserter + ? undefined + : getScrollContainer( node ) || ownerDocument.body; return ( setIsToolbarForced( false ) } + onFocusOutside={ () => { + setIsToolbarForced( false ); + } } shouldAnchorIncludePadding > { ( shouldShowContextualToolbar || isToolbarForced ) && ( @@ -190,12 +217,19 @@ function BlockPopover( { // If the toolbar is being shown because of being forced // it should focus the toolbar right after the mount. focusOnMount={ isToolbarForced } + __experimentalInitialIndex={ + initialToolbarItemIndexRef.current + } + __experimentalOnIndexChange={ ( index ) => { + initialToolbarItemIndexRef.current = index; + } } /> ) } { shouldShowBreadcrumb && ( ) } { showEmptyBlockSideInserter && ( diff --git a/packages/block-editor/src/components/block-list/block-selection-button.js b/packages/block-editor/src/components/block-list/block-selection-button.js index 0462b3dbf28f86..8c7deae94570e7 100644 --- a/packages/block-editor/src/components/block-list/block-selection-button.js +++ b/packages/block-editor/src/components/block-list/block-selection-button.js @@ -9,12 +9,24 @@ import classnames from 'classnames'; import { Button } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { useEffect, useRef } from '@wordpress/element'; -import { BACKSPACE, DELETE } from '@wordpress/keycodes'; +import { + BACKSPACE, + DELETE, + UP, + DOWN, + LEFT, + RIGHT, + TAB, + ESCAPE, + ENTER, + SPACE, +} from '@wordpress/keycodes'; import { getBlockType, __experimentalGetAccessibleBlockLabel as getAccessibleBlockLabel, } from '@wordpress/blocks'; import { speak } from '@wordpress/a11y'; +import { focus } from '@wordpress/dom'; /** * Internal dependencies @@ -30,6 +42,40 @@ function isWindows() { return window.navigator.platform.indexOf( 'Win' ) > -1; } +function selector( select ) { + const { + getSelectedBlockClientId, + getMultiSelectedBlocksEndClientId, + getPreviousBlockClientId, + getNextBlockClientId, + hasBlockMovingClientId, + getBlockIndex, + getBlockRootClientId, + getClientIdsOfDescendants, + canInsertBlockType, + getBlockName, + } = select( 'core/block-editor' ); + + const selectedBlockClientId = getSelectedBlockClientId(); + const selectionEndClientId = getMultiSelectedBlocksEndClientId(); + + return { + selectedBlockClientId, + selectionBeforeEndClientId: getPreviousBlockClientId( + selectionEndClientId || selectedBlockClientId + ), + selectionAfterEndClientId: getNextBlockClientId( + selectionEndClientId || selectedBlockClientId + ), + hasBlockMovingClientId, + getBlockIndex, + getBlockRootClientId, + getClientIdsOfDescendants, + canInsertBlockType, + getBlockName, + }; +} + /** * Block selection button component, displaying the label of the block. If the block * descends from a root block, a button is displayed enabling the user to select @@ -40,7 +86,7 @@ function isWindows() { * * @return {WPComponent} The component to be rendered. */ -function BlockSelectionButton( { clientId, rootClientId, ...props } ) { +function BlockSelectionButton( { clientId, rootClientId, blockElement } ) { const selected = useSelect( ( select ) => { const { @@ -82,12 +128,111 @@ function BlockSelectionButton( { clientId, rootClientId, ...props } ) { } }, [] ); + const { + selectedBlockClientId, + selectionBeforeEndClientId, + selectionAfterEndClientId, + hasBlockMovingClientId, + getBlockIndex, + getBlockRootClientId, + getClientIdsOfDescendants, + } = useSelect( selector, [] ); + const { + selectBlock, + clearSelectedBlock, + setBlockMovingClientId, + moveBlockToPosition, + } = useDispatch( 'core/block-editor' ); + function onKeyDown( event ) { const { keyCode } = event; + const isUp = keyCode === UP; + const isDown = keyCode === DOWN; + const isLeft = keyCode === LEFT; + const isRight = keyCode === RIGHT; + const isTab = keyCode === TAB; + const isEscape = keyCode === ESCAPE; + const isEnter = keyCode === ENTER; + const isSpace = keyCode === SPACE; + const isShift = event.shiftKey; if ( keyCode === BACKSPACE || keyCode === DELETE ) { removeBlock( clientId ); event.preventDefault(); + return; + } + + const navigateUp = ( isTab && isShift ) || isUp; + const navigateDown = ( isTab && ! isShift ) || isDown; + // Move out of current nesting level (no effect if at root level). + const navigateOut = isLeft; + // Move into next nesting level (no effect if the current block has no innerBlocks). + const navigateIn = isRight; + + let focusedBlockUid; + if ( navigateUp ) { + focusedBlockUid = selectionBeforeEndClientId; + } else if ( navigateDown ) { + focusedBlockUid = selectionAfterEndClientId; + } else if ( navigateOut ) { + focusedBlockUid = + getBlockRootClientId( selectedBlockClientId ) ?? + selectedBlockClientId; + } else if ( navigateIn ) { + focusedBlockUid = + getClientIdsOfDescendants( [ selectedBlockClientId ] )[ 0 ] ?? + selectedBlockClientId; + } + const startingBlockClientId = hasBlockMovingClientId(); + + if ( isEscape && startingBlockClientId ) { + setBlockMovingClientId( null ); + } + if ( ( isEnter || isSpace ) && startingBlockClientId ) { + const sourceRoot = getBlockRootClientId( startingBlockClientId ); + const destRoot = getBlockRootClientId( selectedBlockClientId ); + const sourceBlockIndex = getBlockIndex( + startingBlockClientId, + sourceRoot + ); + let destinationBlockIndex = getBlockIndex( + selectedBlockClientId, + destRoot + ); + if ( + sourceBlockIndex < destinationBlockIndex && + sourceRoot === destRoot + ) { + destinationBlockIndex -= 1; + } + moveBlockToPosition( + startingBlockClientId, + sourceRoot, + destRoot, + destinationBlockIndex + ); + selectBlock( startingBlockClientId ); + setBlockMovingClientId( null ); + } + if ( navigateDown || navigateUp || navigateOut || navigateIn ) { + if ( focusedBlockUid ) { + event.preventDefault(); + selectBlock( focusedBlockUid ); + } else if ( isTab && selectedBlockClientId ) { + let nextTabbable; + + if ( navigateDown ) { + nextTabbable = focus.tabbable.findNext( blockElement ); + } else { + nextTabbable = focus.tabbable.findPrevious( blockElement ); + } + + if ( nextTabbable ) { + event.preventDefault(); + nextTabbable.focus(); + clearSelectedBlock(); + } + } } } @@ -107,7 +252,7 @@ function BlockSelectionButton( { clientId, rootClientId, ...props } ) { ); return ( -
+
); - /* eslint-enable jsx-a11y/no-autofocus */ } export default InserterSearchForm; diff --git a/packages/block-editor/src/components/inserter/search-results.js b/packages/block-editor/src/components/inserter/search-results.js new file mode 100644 index 00000000000000..0285fd4a872edf --- /dev/null +++ b/packages/block-editor/src/components/inserter/search-results.js @@ -0,0 +1,172 @@ +/** + * External dependencies + */ +import { orderBy, isEmpty } from 'lodash'; + +/** + * WordPress dependencies + */ +import { useMemo, useEffect } from '@wordpress/element'; +import { __, _n, sprintf } from '@wordpress/i18n'; +import { VisuallyHidden } from '@wordpress/components'; +import { useDebounce, useAsyncList } from '@wordpress/compose'; +import { speak } from '@wordpress/a11y'; + +/** + * Internal dependencies + */ +import BlockTypesList from '../block-types-list'; +import BlockPatternsList from '../block-patterns-list'; +import __experimentalInserterMenuExtension from '../inserter-menu-extension'; +import InserterPanel from './panel'; +import InserterNoResults from './no-results'; +import useInsertionPoint from './hooks/use-insertion-point'; +import usePatternsState from './hooks/use-patterns-state'; +import useBlockTypesState from './hooks/use-block-types-state'; +import { searchBlockItems, searchItems } from './search-items'; + +function InserterSearchResults( { + filterValue, + onSelect, + onHover, + rootClientId, + clientId, + isAppender, + selectBlockOnInsert, + maxBlockPatterns, + maxBlockTypes, + showBlockDirectory = false, + isDraggable = true, +} ) { + const debouncedSpeak = useDebounce( speak, 500 ); + + const [ destinationRootClientId, onInsertBlocks ] = useInsertionPoint( { + onSelect, + rootClientId, + clientId, + isAppender, + selectBlockOnInsert, + } ); + const [ + blockTypes, + blockTypeCategories, + blockTypeCollections, + onSelectBlockType, + ] = useBlockTypesState( destinationRootClientId, onInsertBlocks ); + const [ patterns, , onSelectBlockPattern ] = usePatternsState( + onInsertBlocks + ); + + const filteredBlockTypes = useMemo( () => { + const results = searchBlockItems( + orderBy( blockTypes, [ 'frecency' ], [ 'desc' ] ), + blockTypeCategories, + blockTypeCollections, + filterValue + ); + + return maxBlockTypes !== undefined + ? results.slice( 0, maxBlockTypes ) + : results; + }, [ + filterValue, + blockTypes, + blockTypeCategories, + blockTypeCollections, + maxBlockTypes, + ] ); + + const filteredBlockPatterns = useMemo( () => { + const results = searchItems( patterns, filterValue ); + return maxBlockPatterns !== undefined + ? results.slice( 0, maxBlockPatterns ) + : results; + }, [ filterValue, patterns, maxBlockPatterns ] ); + + // Announce search results on change + useEffect( () => { + if ( ! filterValue ) { + return; + } + const count = filteredBlockTypes.length + filteredBlockPatterns.length; + const resultsFoundMessage = sprintf( + /* translators: %d: number of results. */ + _n( '%d result found.', '%d results found.', count ), + count + ); + debouncedSpeak( resultsFoundMessage ); + }, [ filterValue, debouncedSpeak ] ); + + const currentShownPatterns = useAsyncList( filteredBlockPatterns ); + + const hasItems = + ! isEmpty( filteredBlockTypes ) || ! isEmpty( filteredBlockPatterns ); + + return ( + <> + { ! showBlockDirectory && ! hasItems && } + + { !! filteredBlockTypes.length && ( + { __( 'Blocks' ) } + } + > + + + ) } + + { !! filteredBlockTypes.length && + !! filteredBlockPatterns.length && ( +
+ ) } + + { !! filteredBlockPatterns.length && ( + + { __( 'Block Patterns' ) } + + } + > +
+ +
+
+ ) } + + { showBlockDirectory && ( + <__experimentalInserterMenuExtension.Slot + fillProps={ { + onSelect: onSelectBlockType, + onHover, + filterValue, + hasItems, + } } + > + { ( fills ) => { + if ( fills.length ) { + return fills; + } + if ( ! hasItems ) { + return ; + } + return null; + } } + + ) } + + ); +} + +export default InserterSearchResults; diff --git a/packages/block-editor/src/components/inserter/style.native.scss b/packages/block-editor/src/components/inserter/style.native.scss index 19db583d514864..77456c9587d1dd 100644 --- a/packages/block-editor/src/components/inserter/style.native.scss +++ b/packages/block-editor/src/components/inserter/style.native.scss @@ -15,3 +15,7 @@ .columnPadding { padding: $grid-unit-20; } + +.list { + padding-bottom: 20; +} diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index 96df823e35faf5..1640d6616371e3 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -166,8 +166,7 @@ $block-inserter-tabs-height: 44px; } .block-editor-inserter__panel-title, -.block-editor-inserter__panel-title button, -.components-custom-select-control__menu li { +.block-editor-inserter__panel-title button { margin: 0 $grid-unit-15 0 0; color: $gray-700; text-transform: uppercase; @@ -276,6 +275,7 @@ $block-inserter-tabs-height: 44px; border-top: $border-width solid $gray-300; padding: $grid-unit-20; flex-shrink: 0; + position: relative; // prevents overscroll when block library is open } .block-editor-inserter__manage-reusable-blocks-container { diff --git a/packages/block-editor/src/components/inserter/test/block-types-tab.js b/packages/block-editor/src/components/inserter/test/block-types-tab.js index c0138be4e2d71e..2e0d465ce9f414 100644 --- a/packages/block-editor/src/components/inserter/test/block-types-tab.js +++ b/packages/block-editor/src/components/inserter/test/block-types-tab.js @@ -6,6 +6,7 @@ import { render, fireEvent } from '@testing-library/react'; /** * WordPress dependencies */ +import { registerBlockType, unregisterBlockType } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; /** @@ -56,21 +57,21 @@ const initializeAllClosedMenuState = ( propOverrides ) => { return result; }; -const assertNoResultsMessageToBePresent = ( element ) => { - const noResultsMessage = element.querySelector( - '.block-editor-inserter__no-results' - ); - expect( noResultsMessage.textContent ).toEqual( 'No results found.' ); -}; - -const assertNoResultsMessageNotToBePresent = ( element ) => { - const noResultsMessage = element.querySelector( - '.block-editor-inserter__no-results' - ); - expect( noResultsMessage ).toBe( null ); -}; - describe( 'InserterMenu', () => { + beforeAll( () => { + items.forEach( ( item ) => { + registerBlockType( item.name, { + save: () => {}, + title: item.name, + edit: () => {}, + } ); + } ); + } ); + afterAll( () => { + items.forEach( ( item ) => { + unregisterBlockType( item.name ); + } ); + } ); beforeEach( () => { debouncedSpeak.mockClear(); @@ -98,8 +99,6 @@ describe( 'InserterMenu', () => { ); expect( visibleBlocks ).toBe( null ); - - assertNoResultsMessageToBePresent( container ); } ); it( 'should show items from the embed category in the embed tab', () => { @@ -118,8 +117,6 @@ describe( 'InserterMenu', () => { expect( blocks ).toHaveLength( 2 ); expect( blocks[ 0 ].textContent ).toBe( 'YouTube' ); expect( blocks[ 1 ].textContent ).toBe( 'A Paragraph Embed' ); - - assertNoResultsMessageNotToBePresent( container ); } ); it( 'should show the common category blocks', () => { @@ -139,8 +136,6 @@ describe( 'InserterMenu', () => { expect( blocks[ 0 ].textContent ).toBe( 'Paragraph' ); expect( blocks[ 1 ].textContent ).toBe( 'Advanced Paragraph' ); expect( blocks[ 2 ].textContent ).toBe( 'Some Other Block' ); - - assertNoResultsMessageNotToBePresent( container ); } ); it( 'displays child blocks UI when root block has child blocks', () => { @@ -153,8 +148,6 @@ describe( 'InserterMenu', () => { ); expect( childBlocksContent ).not.toBeNull(); - - assertNoResultsMessageNotToBePresent( container ); } ); it( 'should disable items with `isDisabled`', () => { @@ -169,81 +162,4 @@ describe( 'InserterMenu', () => { expect( disabledBlocks ).toHaveLength( 1 ); expect( disabledBlocks[ 0 ].textContent ).toBe( 'More' ); } ); - - it( 'should allow searching for items', () => { - const { container } = render( - - ); - - const matchingCategories = container.querySelectorAll( - '.block-editor-inserter__panel-title' - ); - - expect( matchingCategories ).toHaveLength( 3 ); - expect( matchingCategories[ 0 ].textContent ).toBe( 'Text' ); - expect( matchingCategories[ 1 ].textContent ).toBe( 'Embeds' ); - expect( matchingCategories[ 2 ].textContent ).toBe( 'Core' ); // "Core" namespace collection - - const blocks = container.querySelectorAll( - '.block-editor-block-types-list__item-title' - ); - - // There are five buttons present for 3 total distinct results. The - // additional two account for the collection results (repeated). - expect( blocks ).toHaveLength( 5 ); - expect( debouncedSpeak ).toHaveBeenCalledWith( '3 results found.' ); - - // Default block results. - expect( blocks[ 0 ].textContent ).toBe( 'Paragraph' ); - expect( blocks[ 1 ].textContent ).toBe( 'Advanced Paragraph' ); - expect( blocks[ 2 ].textContent ).toBe( 'A Paragraph Embed' ); - - // Collection results. - expect( blocks[ 3 ].textContent ).toBe( 'Paragraph' ); - expect( blocks[ 4 ].textContent ).toBe( 'Advanced Paragraph' ); - - assertNoResultsMessageNotToBePresent( container ); - } ); - - it( 'should speak after any change in search term', () => { - // The search result count should always be announced any time the user - // changes the search term, even if it results in the same count. - // - // See: https://github.com/WordPress/gutenberg/pull/22279#discussion_r423317161 - const { rerender } = render( - - ); - - rerender( ); - rerender( ); - - expect( debouncedSpeak ).toHaveBeenCalledTimes( 2 ); - expect( debouncedSpeak.mock.calls[ 0 ][ 0 ] ).toBe( '1 result found.' ); - expect( debouncedSpeak.mock.calls[ 1 ][ 0 ] ).toBe( '1 result found.' ); - } ); - - it( 'should trim whitespace of search terms', () => { - const { container } = render( - - ); - - const matchingCategories = container.querySelectorAll( - '.block-editor-inserter__panel-title' - ); - - expect( matchingCategories ).toHaveLength( 3 ); - expect( matchingCategories[ 0 ].textContent ).toBe( 'Text' ); - expect( matchingCategories[ 1 ].textContent ).toBe( 'Embeds' ); - - const blocks = container.querySelectorAll( - '.block-editor-block-types-list__item-title' - ); - - expect( blocks ).toHaveLength( 5 ); - expect( blocks[ 0 ].textContent ).toBe( 'Paragraph' ); - expect( blocks[ 1 ].textContent ).toBe( 'Advanced Paragraph' ); - expect( blocks[ 2 ].textContent ).toBe( 'A Paragraph Embed' ); - - assertNoResultsMessageNotToBePresent( container ); - } ); } ); diff --git a/packages/block-editor/src/components/inserter/test/reusable-blocks-tab.js b/packages/block-editor/src/components/inserter/test/reusable-blocks-tab.js index 0c8d31b2a1fb55..ce2ce742c81831 100644 --- a/packages/block-editor/src/components/inserter/test/reusable-blocks-tab.js +++ b/packages/block-editor/src/components/inserter/test/reusable-blocks-tab.js @@ -6,6 +6,7 @@ import { render, fireEvent } from '@testing-library/react'; /** * WordPress dependencies */ +import { registerBlockType, unregisterBlockType } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; /** @@ -50,21 +51,17 @@ const initializeAllClosedMenuState = ( propOverrides ) => { return result; }; -const assertNoResultsMessageToBePresent = ( element ) => { - const noResultsMessage = element.querySelector( - '.block-editor-inserter__no-results' - ); - expect( noResultsMessage.textContent ).toEqual( 'No results found.' ); -}; - -const assertNoResultsMessageNotToBePresent = ( element ) => { - const noResultsMessage = element.querySelector( - '.block-editor-inserter__no-results' - ); - expect( noResultsMessage ).toBe( null ); -}; - describe( 'InserterMenu', () => { + beforeAll( () => { + registerBlockType( 'core/block', { + save: () => {}, + title: 'reusable block', + edit: () => {}, + } ); + } ); + afterAll( () => { + unregisterBlockType( 'core/block' ); + } ); beforeEach( () => { debouncedSpeak.mockClear(); @@ -92,8 +89,6 @@ describe( 'InserterMenu', () => { ); expect( visibleBlocks ).toBe( null ); - - assertNoResultsMessageToBePresent( container ); } ); it( 'should list reusable blocks', () => { @@ -104,41 +99,6 @@ describe( 'InserterMenu', () => { expect( blocks ).toHaveLength( 1 ); expect( blocks[ 0 ].textContent ).toBe( 'My reusable block' ); - - assertNoResultsMessageNotToBePresent( container ); - } ); - - it( 'should allow searching for reusable blocks by title', () => { - const { container } = render( - - ); - - const blocks = container.querySelectorAll( - '.block-editor-block-types-list__item-title' - ); - - expect( blocks ).toHaveLength( 1 ); - expect( debouncedSpeak ).toHaveBeenCalledWith( '1 result found.' ); - expect( blocks[ 0 ].textContent ).toBe( 'My reusable block' ); - - assertNoResultsMessageNotToBePresent( container ); - } ); - - it( 'should speak after any change in search term', () => { - // The search result count should always be announced any time the user - // changes the search term, even if it results in the same count. - // - // See: https://github.com/WordPress/gutenberg/pull/22279#discussion_r423317161 - const { rerender } = render( - - ); - - rerender( ); - rerender( ); - - expect( debouncedSpeak ).toHaveBeenCalledTimes( 2 ); - expect( debouncedSpeak.mock.calls[ 0 ][ 0 ] ).toBe( '1 result found.' ); - expect( debouncedSpeak.mock.calls[ 1 ][ 0 ] ).toBe( '1 result found.' ); } ); it( 'should trim whitespace of search terms', () => { @@ -152,7 +112,5 @@ describe( 'InserterMenu', () => { expect( blocks ).toHaveLength( 1 ); expect( blocks[ 0 ].textContent ).toBe( 'My reusable block' ); - - assertNoResultsMessageNotToBePresent( container ); } ); } ); diff --git a/packages/block-editor/src/components/inspector-controls/README.md b/packages/block-editor/src/components/inspector-controls/README.md index b43a4fbe7fd267..57937e806dd7e5 100644 --- a/packages/block-editor/src/components/inspector-controls/README.md +++ b/packages/block-editor/src/components/inspector-controls/README.md @@ -13,7 +13,8 @@ var el = wp.element.createElement, Fragment = wp.element.Fragment, registerBlockType = wp.blocks.registerBlockType, RichText = wp.editor.RichText, - InspectorControls = wp.editor.InspectorControls, + InspectorControls = wp.blockEditor.InspectorControls, + useBlockProps = wp.blockEditor.useBlockProps, CheckboxControl = wp.components.CheckboxControl, RadioControl = wp.components.RadioControl, TextControl = wp.components.TextControl, @@ -21,6 +22,8 @@ var el = wp.element.createElement, SelectControl = wp.components.SelectControl; registerBlockType( 'my-plugin/inspector-controls-example', { + apiVersion: 2, + title: 'Inspector controls example', icon: 'universal-access-alt', @@ -53,6 +56,8 @@ registerBlockType( 'my-plugin/inspector-controls-example', { }, edit: function( props ) { + var blockProps = useBlockProps(); + var content = props.attributes.content, checkboxField = props.attributes.checkboxField, radioField = props.attributes.radioField, @@ -161,18 +166,19 @@ registerBlockType( 'my-plugin/inspector-controls-example', { ), el( RichText, - { + Object.assing( blockProps, { key: 'editable', tagName: 'p', onChange: onChangeContent, value: content - } + } ) ) ) ); }, save: function( props ) { + var blockProps = useBlockProps.save(); var content = props.attributes.content, checkboxField = props.attributes.checkboxField, radioField = props.attributes.radioField, @@ -182,7 +188,7 @@ registerBlockType( 'my-plugin/inspector-controls-example', { return el( 'div', - null, + blockProps, el( RichText.Content, { @@ -236,19 +242,22 @@ registerBlockType( 'my-plugin/inspector-controls-example', { {% ESNext %} ```js import { registerBlockType } from '@wordpress/blocks'; -const { +import { CheckboxControl, RadioControl, TextControl, ToggleControl, - SelectControl, -} = wp.components; -const { + SelectControl +} from '@wordpress/components'; +import { RichText, InspectorControls, -} = wp.editor; + useBlockProps +} from '@wordpress/block-editor'; registerBlockType( 'my-plugin/inspector-controls-example', { + apiVersion: 2, + title: 'Inspector controls example', icon: 'universal-access-alt', @@ -281,6 +290,7 @@ registerBlockType( 'my-plugin/inspector-controls-example', { }, edit( { attributes, setAttributes } ) { + const blockProps = useBlockProps(); const { content, checkboxField, radioField, textField, toggleField, selectField } = attributes; function onChangeContent( newContent ) { @@ -360,6 +370,7 @@ registerBlockType( 'my-plugin/inspector-controls-example', { +
{ registerShortcut( { name: 'core/block-editor/duplicate', diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 470aa862bb1190..d2bac48450bb0d 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -120,6 +120,7 @@ function LinkControl( { withCreateSuggestion = true; } + const isMounting = useRef( true ); const wrapperNode = useRef(); const [ internalInputValue, setInternalInputValue ] = useState( ( value && value.url ) || '' @@ -142,19 +143,20 @@ function LinkControl( { }, [ forceIsEditingLink ] ); useEffect( () => { - // When `isEditingLink` is set to `false`, a focus loss could occur + if ( isMounting.current ) { + isMounting.current = false; + return; + } + // When `isEditingLink` changes, a focus loss could occur // since the link input may be removed from the DOM. To avoid this, // reinstate focus to a suitable target if focus has in-fact been lost. // Note that the check is necessary because while typically unsetting // edit mode would render the read-only mode's link element, it isn't // guaranteed. The link input may continue to be shown if the next value // is still unassigned after calling `onChange`. - const hadFocusLoss = - isEndingEditWithFocus.current && - wrapperNode.current && - ! wrapperNode.current.contains( - wrapperNode.current.ownerDocument.activeElement - ); + const hadFocusLoss = ! wrapperNode.current.contains( + wrapperNode.current.ownerDocument.activeElement + ); if ( hadFocusLoss ) { // Prefer to focus a natural focusable descendent of the wrapper, diff --git a/packages/block-editor/src/components/link-control/search-create-button.js b/packages/block-editor/src/components/link-control/search-create-button.js index 5bb385f31f8e26..679c5df55c0fca 100644 --- a/packages/block-editor/src/components/link-control/search-create-button.js +++ b/packages/block-editor/src/components/link-control/search-create-button.js @@ -10,7 +10,7 @@ import { isFunction } from 'lodash'; import { __, sprintf } from '@wordpress/i18n'; import { Button } from '@wordpress/components'; import { createInterpolateElement } from '@wordpress/element'; -import { Icon, plusCircle } from '@wordpress/icons'; +import { Icon, plus } from '@wordpress/icons'; export const LinkControlSearchCreate = ( { searchTerm, @@ -50,7 +50,7 @@ export const LinkControlSearchCreate = ( { > diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss index ae7d4c8b9b89dc..c9249011bc4d22 100644 --- a/packages/block-editor/src/components/link-control/style.scss +++ b/packages/block-editor/src/components/link-control/style.scss @@ -59,6 +59,10 @@ $block-editor-link-control-number-of-actions: 1; right: $grid-unit-20 + 1px + min($grid-unit-10, ( 40px - $button-size ) / 2); } +.components-button .block-editor-link-control__search-submit .has-icon { + margin: -1px; +} + .block-editor-link-control__search-results-wrapper { position: relative; margin-top: -$grid-unit-20 + 1px; @@ -78,21 +82,19 @@ $block-editor-link-control-number-of-actions: 1; height: $grid-unit-20/2; top: 0; bottom: auto; - background: linear-gradient(to bottom, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%); } &::after { height: $grid-unit-20; bottom: 0; top: auto; - background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); } } .block-editor-link-control__search-results-label { - padding: 15px 30px 0 30px; + padding: $grid-unit-20 $grid-unit-40 0; display: block; - font-size: 1.1em; + font-weight: 600; } .block-editor-link-control__search-results { @@ -116,7 +118,7 @@ $block-editor-link-control-number-of-actions: 1; width: 100%; border: none; text-align: left; - padding: 10px 15px; + padding: $grid-unit-15 $grid-unit-20; border-radius: 5px; height: auto; @@ -211,9 +213,8 @@ $block-editor-link-control-number-of-actions: 1; // Separate Create button when following other suggestions. .components-button + .block-editor-link-control__search-create { - margin-top: 20px; overflow: visible; - padding: 12px 15px; + padding: $grid-unit-15 $grid-unit-20; // Create fake border. We cannot use border because the button has a border // radius applied to it @@ -224,7 +225,6 @@ $block-editor-link-control-number-of-actions: 1; left: 0; display: block; width: 100%; - border-top: 1px solid $gray-300; } } diff --git a/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap index abb82e4ce4c1a5..dd0bc6f80877ce 100644 --- a/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Basic rendering should render 1`] = `""`; +exports[`Basic rendering should render 1`] = `""`; diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 2c1efc9352bdd0..53d04f6e023336 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -8,7 +8,7 @@ import { default as lodash, first, last, nth, uniqueId } from 'lodash'; /** * WordPress dependencies */ -import { useState, useRef } from '@wordpress/element'; +import { useState } from '@wordpress/element'; import { UP, DOWN, ENTER } from '@wordpress/keycodes'; /** * Internal dependencies @@ -594,12 +594,13 @@ describe( 'Default search suggestions', () => { Simulate.click( currentLinkBtn ); } ); + const searchInput = getURLInput(); + searchInput.focus(); + await eventLoopTick(); const searchResultElements = getSearchResults(); - const searchInput = getURLInput(); - // search input is set to the URL value expect( searchInput.value ).toEqual( fauxEntitySuggestions[ 0 ].url ); @@ -1372,6 +1373,7 @@ describe( 'Selecting links', () => { // Search Input UI const searchInput = getURLInput(); + searchInput.focus(); const form = container.querySelector( 'form' ); // Simulate searching for a term @@ -1540,54 +1542,6 @@ describe( 'Selecting links', () => { expect( mockFetchSearchSuggestions ).toHaveBeenCalledTimes( 1 ); } ); } ); - - it( 'does not forcefully regain focus if onChange handler had shifted it', () => { - // Regression: Previously, there had been issues where if `onChange` - // would programmatically shift focus, LinkControl would try to force it - // back, based on its internal logic to determine whether it had focus - // when finishing an edit occuring _before_ `onChange` having been run. - // - // See: https://github.com/WordPress/gutenberg/pull/19462 - - const LinkControlConsumer = () => { - const focusTarget = useRef(); - - return ( - <> -
- focusTarget.current.focus() } - /> - - ); - }; - - act( () => { - render( , container ); - } ); - - // Change value. - const form = container.querySelector( 'form' ); - const searchInput = getURLInput(); - - // Simulate searching for a term - act( () => { - Simulate.change( searchInput, { - target: { value: 'https://example.com' }, - } ); - } ); - act( () => { - Simulate.keyDown( searchInput, { keyCode: ENTER } ); - } ); - act( () => { - Simulate.submit( form ); - } ); - - const isExpectedFocusTarget = document.activeElement.hasAttribute( - 'data-expected' - ); - expect( isExpectedFocusTarget ).toBe( true ); - } ); } ); describe( 'Addition Settings UI', () => { diff --git a/packages/block-editor/src/components/media-replace-flow/index.js b/packages/block-editor/src/components/media-replace-flow/index.js index 4230ca6fd253b5..7af1dfa2caf182 100644 --- a/packages/block-editor/src/components/media-replace-flow/index.js +++ b/packages/block-editor/src/components/media-replace-flow/index.js @@ -19,9 +19,10 @@ import { withFilters, } from '@wordpress/components'; import { withDispatch, useSelect } from '@wordpress/data'; -import { DOWN } from '@wordpress/keycodes'; +import { DOWN, TAB, ESCAPE } from '@wordpress/keycodes'; import { compose } from '@wordpress/compose'; import { upload, media as mediaIcon } from '@wordpress/icons'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies @@ -168,10 +169,18 @@ const MediaReplaceFlow = ( {
{ - event.stopPropagation(); + if ( + ! [ TAB, ESCAPE ].includes( event.keyCode ) + ) { + event.stopPropagation(); + } } } onKeyPress={ ( event ) => { - event.stopPropagation(); + if ( + ! [ TAB, ESCAPE ].includes( event.keyCode ) + ) { + event.stopPropagation(); + } } } > @@ -197,7 +206,7 @@ const MediaReplaceFlow = ( { export default compose( [ withDispatch( ( dispatch ) => { - const { createNotice, removeNotice } = dispatch( 'core/notices' ); + const { createNotice, removeNotice } = dispatch( noticesStore ); return { createNotice, removeNotice, diff --git a/packages/block-editor/src/components/media-upload-progress/index.native.js b/packages/block-editor/src/components/media-upload-progress/index.native.js index c25e23f9659cd5..b0890e95df5834 100644 --- a/packages/block-editor/src/components/media-upload-progress/index.native.js +++ b/packages/block-editor/src/components/media-upload-progress/index.native.js @@ -125,13 +125,16 @@ export class MediaUploadProgress extends React.Component { 'Failed to insert media.\nPlease tap for options.' ); + const progressBarStyle = [ + styles.progressBar, + showSpinner || styles.progressBarHidden, + ]; + return ( - { showSpinner && ( - - - - ) } + + { showSpinner && } + { renderContent( { isUploadInProgress, isUploadFailed, diff --git a/packages/block-editor/src/components/media-upload-progress/styles.native.scss b/packages/block-editor/src/components/media-upload-progress/styles.native.scss index 2178be1968b1c5..3a6f831d0154e3 100644 --- a/packages/block-editor/src/components/media-upload-progress/styles.native.scss +++ b/packages/block-editor/src/components/media-upload-progress/styles.native.scss @@ -6,4 +6,10 @@ .progressBar { background-color: $gray-lighten-30; z-index: 1; + height: 6px; + margin-bottom: -6px; +} + +.progressBarHidden { + background-color: transparent; } diff --git a/packages/block-editor/src/components/media-upload/index.native.js b/packages/block-editor/src/components/media-upload/index.native.js index d413171cd114be..1147289c8d6b1b 100644 --- a/packages/block-editor/src/components/media-upload/index.native.js +++ b/packages/block-editor/src/components/media-upload/index.native.js @@ -17,12 +17,13 @@ import { capturePhoto, captureVideo, image, - video, wordpress, + mobile, } from '@wordpress/icons'; export const MEDIA_TYPE_IMAGE = 'image'; export const MEDIA_TYPE_VIDEO = 'video'; +export const MEDIA_TYPE_ANY = 'any'; export const OPTION_TAKE_VIDEO = __( 'Take a Video' ); export const OPTION_TAKE_PHOTO = __( 'Take a Photo' ); @@ -86,7 +87,7 @@ export class MediaUpload extends React.Component { id: mediaSources.siteMediaLibrary, value: mediaSources.siteMediaLibrary, label: __( 'WordPress Media Library' ), - types: [ MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO ], + types: [ MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO, MEDIA_TYPE_ANY ], icon: wordpress, mediaLibrary: true, }; @@ -124,17 +125,7 @@ export class MediaUpload extends React.Component { } getChooseFromDeviceIcon() { - const { allowedTypes = [] } = this.props; - - const isOneType = allowedTypes.length === 1; - const isImage = isOneType && allowedTypes.includes( MEDIA_TYPE_IMAGE ); - const isVideo = isOneType && allowedTypes.includes( MEDIA_TYPE_VIDEO ); - - if ( isImage || ! isOneType ) { - return image; - } else if ( isVideo ) { - return video; - } + return mobile; } onPickerPresent() { @@ -164,6 +155,8 @@ export class MediaUpload extends React.Component { const isOneType = allowedTypes.length === 1; const isImage = isOneType && allowedTypes.includes( MEDIA_TYPE_IMAGE ); const isVideo = isOneType && allowedTypes.includes( MEDIA_TYPE_VIDEO ); + const isAnyType = isOneType && allowedTypes.includes( MEDIA_TYPE_ANY ); + const isImageOrVideo = allowedTypes.length === 2 && allowedTypes.includes( MEDIA_TYPE_IMAGE ) && @@ -190,6 +183,8 @@ export class MediaUpload extends React.Component { } else { pickerTitle = __( 'Choose image or video' ); } + } else if ( isAnyType ) { + pickerTitle = __( 'Choose file' ); } const getMediaOptions = () => ( diff --git a/packages/block-editor/src/components/multi-select-scroll-into-view/index.js b/packages/block-editor/src/components/multi-select-scroll-into-view/index.js index 025a2628bff180..fca97d6621f833 100644 --- a/packages/block-editor/src/components/multi-select-scroll-into-view/index.js +++ b/packages/block-editor/src/components/multi-select-scroll-into-view/index.js @@ -6,7 +6,7 @@ import scrollIntoView from 'dom-scroll-into-view'; /** * WordPress dependencies */ -import { useEffect } from '@wordpress/element'; +import { useEffect, useRef } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { getScrollContainer } from '@wordpress/dom'; @@ -15,35 +15,34 @@ import { getScrollContainer } from '@wordpress/dom'; */ import { getBlockDOMNode } from '../../utils/dom'; -/** - * Scrolls the multi block selection end into view if not in view already. This - * is important to do after selection by keyboard. - */ -export default function MultiSelectScrollIntoView() { - const selector = ( select ) => { +export function useScrollMultiSelectionIntoView( ref ) { + const selectionEnd = useSelect( ( select ) => { const { getBlockSelectionEnd, hasMultiSelection, isMultiSelecting, } = select( 'core/block-editor' ); - return { - selectionEnd: getBlockSelectionEnd(), - isMultiSelection: hasMultiSelection(), - isMultiSelecting: isMultiSelecting(), - }; - }; - const { isMultiSelection, selectionEnd, isMultiSelecting } = useSelect( - selector, - [] - ); + const blockSelectionEnd = getBlockSelectionEnd(); + + if ( + ! blockSelectionEnd || + isMultiSelecting() || + ! hasMultiSelection() + ) { + return; + } + + return blockSelectionEnd; + }, [] ); useEffect( () => { - if ( ! selectionEnd || isMultiSelecting || ! isMultiSelection ) { + if ( ! selectionEnd ) { return; } - const extentNode = getBlockDOMNode( selectionEnd ); + const { ownerDocument } = ref.current; + const extentNode = getBlockDOMNode( selectionEnd, ownerDocument ); if ( ! extentNode ) { return; @@ -60,7 +59,15 @@ export default function MultiSelectScrollIntoView() { scrollIntoView( extentNode, scrollContainer, { onlyScrollIfNeeded: true, } ); - }, [ isMultiSelection, selectionEnd, isMultiSelecting ] ); + }, [ selectionEnd ] ); +} - return null; +/** + * Scrolls the multi block selection end into view if not in view already. This + * is important to do after selection by keyboard. + */ +export default function MultiSelectScrollIntoView() { + const ref = useRef(); + useScrollMultiSelectionIntoView( ref ); + return
; } diff --git a/packages/block-editor/src/components/navigable-toolbar/index.js b/packages/block-editor/src/components/navigable-toolbar/index.js index 5b3fad6e09138e..aa516640ae7365 100644 --- a/packages/block-editor/src/components/navigable-toolbar/index.js +++ b/packages/block-editor/src/components/navigable-toolbar/index.js @@ -18,6 +18,14 @@ function hasOnlyToolbarItem( elements ) { return ! elements.some( ( element ) => ! ( dataProp in element.dataset ) ); } +function getAllToolbarItemsIn( container ) { + return Array.from( container.querySelectorAll( '[data-toolbar-item]' ) ); +} + +function hasFocusWithin( container ) { + return container.contains( container.ownerDocument.activeElement ); +} + function focusFirstTabbableIn( container ) { const [ firstTabbable ] = focus.tabbable.find( container ); if ( firstTabbable ) { @@ -74,14 +82,22 @@ function useIsAccessibleToolbar( ref ) { return isAccessibleToolbar; } -function useToolbarFocus( ref, focusOnMount, isAccessibleToolbar ) { +function useToolbarFocus( + ref, + focusOnMount, + isAccessibleToolbar, + defaultIndex, + onIndexChange +) { // Make sure we don't use modified versions of this prop const [ initialFocusOnMount ] = useState( focusOnMount ); + const [ initialIndex ] = useState( defaultIndex ); const focusToolbar = useCallback( () => { focusFirstTabbableIn( ref.current ); }, [] ); + // Focus on toolbar when pressing alt+F10 when the toolbar is visible useShortcut( 'core/block-editor/focus-toolbar', focusToolbar, { bindGlobal: true, eventName: 'keydown', @@ -92,21 +108,55 @@ function useToolbarFocus( ref, focusOnMount, isAccessibleToolbar ) { focusToolbar(); } }, [ isAccessibleToolbar, initialFocusOnMount, focusToolbar ] ); -} -function NavigableToolbar( { children, focusOnMount, ...props } ) { - const wrapper = useRef(); - const isAccessibleToolbar = useIsAccessibleToolbar( wrapper ); + useEffect( () => { + // If initialIndex is passed, we focus on that toolbar item when the + // toolbar gets mounted and initial focus is not forced. + // We have to wait for the next browser paint because block controls aren't + // rendered right away when the toolbar gets mounted. + let raf = 0; + if ( initialIndex && ! initialFocusOnMount ) { + raf = window.requestAnimationFrame( () => { + const items = getAllToolbarItemsIn( ref.current ); + const index = initialIndex || 0; + if ( items[ index ] && hasFocusWithin( ref.current ) ) { + items[ index ].focus(); + } + } ); + } + return () => { + window.cancelAnimationFrame( raf ); + if ( ! onIndexChange ) return; + // When the toolbar element is unmounted and onIndexChange is passed, we + // pass the focused toolbar item index so it can be hydrated later. + const items = getAllToolbarItemsIn( ref.current ); + const index = items.findIndex( ( item ) => item.tabIndex === 0 ); + onIndexChange( index ); + }; + }, [ initialIndex, initialFocusOnMount ] ); +} - useToolbarFocus( wrapper, focusOnMount, isAccessibleToolbar ); +function NavigableToolbar( { + children, + focusOnMount, + __experimentalInitialIndex: initialIndex, + __experimentalOnIndexChange: onIndexChange, + ...props +} ) { + const ref = useRef(); + const isAccessibleToolbar = useIsAccessibleToolbar( ref ); + + useToolbarFocus( + ref, + focusOnMount, + isAccessibleToolbar, + initialIndex, + onIndexChange + ); if ( isAccessibleToolbar ) { return ( - + { children } ); @@ -116,7 +166,7 @@ function NavigableToolbar( { children, focusOnMount, ...props } ) { { children } diff --git a/packages/block-editor/src/components/observe-typing/index.js b/packages/block-editor/src/components/observe-typing/index.js index 24dc85c6be22bd..e33628c92b374a 100644 --- a/packages/block-editor/src/components/observe-typing/index.js +++ b/packages/block-editor/src/components/observe-typing/index.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { over } from 'lodash'; - /** * WordPress dependencies */ @@ -19,14 +14,20 @@ import { ESCAPE, TAB, } from '@wordpress/keycodes'; -import { withSafeTimeout } from '@wordpress/compose'; /** * Set of key codes upon which typing is to be initiated on a keydown event. * - * @type {number[]} + * @type {Set} */ -const KEY_DOWN_ELIGIBLE_KEY_CODES = [ UP, RIGHT, DOWN, LEFT, ENTER, BACKSPACE ]; +const KEY_DOWN_ELIGIBLE_KEY_CODES = new Set( [ + UP, + RIGHT, + DOWN, + LEFT, + ENTER, + BACKSPACE, +] ); /** * Returns true if a given keydown event can be inferred as intent to start @@ -39,163 +40,175 @@ const KEY_DOWN_ELIGIBLE_KEY_CODES = [ UP, RIGHT, DOWN, LEFT, ENTER, BACKSPACE ]; */ function isKeyDownEligibleForStartTyping( event ) { const { keyCode, shiftKey } = event; - return ! shiftKey && KEY_DOWN_ELIGIBLE_KEY_CODES.includes( keyCode ); + return ! shiftKey && KEY_DOWN_ELIGIBLE_KEY_CODES.has( keyCode ); } -function ObserveTyping( { children, setTimeout: setSafeTimeout } ) { - const typingContainer = useRef(); - const lastMouseMove = useRef(); +export function useTypingObserver( ref ) { const isTyping = useSelect( ( select ) => select( 'core/block-editor' ).isTyping() ); const { startTyping, stopTyping } = useDispatch( 'core/block-editor' ); + useEffect( () => { - toggleEventBindings( isTyping ); - return () => toggleEventBindings( false ); - }, [ isTyping ] ); - - /** - * Bind or unbind events to the document when typing has started or stopped - * respectively, or when component has become unmounted. - * - * @param {boolean} isBound Whether event bindings should be applied. - */ - function toggleEventBindings( isBound ) { - const bindFn = isBound ? 'addEventListener' : 'removeEventListener'; - typingContainer.current.ownerDocument[ bindFn ]( - 'selectionchange', - stopTypingOnSelectionUncollapse - ); - typingContainer.current.ownerDocument[ bindFn ]( - 'mousemove', - stopTypingOnMouseMove - ); - document[ bindFn ]( 'mousemove', stopTypingOnMouseMove ); - } - - /** - * On mouse move, unset typing flag if user has moved cursor. - * - * @param {MouseEvent} event Mousemove event. - */ - function stopTypingOnMouseMove( event ) { - const { clientX, clientY } = event; - - // We need to check that the mouse really moved because Safari triggers - // mousemove events when shift or ctrl are pressed. - if ( lastMouseMove.current ) { - const { - clientX: lastClientX, - clientY: lastClientY, - } = lastMouseMove.current; - - if ( lastClientX !== clientX || lastClientY !== clientY ) { - stopTyping(); + const element = ref.current; + const { ownerDocument } = element; + const { defaultView } = ownerDocument; + + // Listeners to stop typing should only be added when typing. + // Listeners to start typing should only be added when not typing. + if ( isTyping ) { + let timerId; + + /** + * Stops typing when focus transitions to a non-text field element. + * + * @param {FocusEvent} event Focus event. + */ + function stopTypingOnNonTextField( event ) { + const { target } = event; + + // Since focus to a non-text field via arrow key will trigger + // before the keydown event, wait until after current stack + // before evaluating whether typing is to be stopped. Otherwise, + // typing will re-start. + timerId = defaultView.setTimeout( () => { + if ( ! isTextField( target ) ) { + stopTyping(); + } + } ); + } + + /** + * Unsets typing flag if user presses Escape while typing flag is + * active. + * + * @param {KeyboardEvent} event Keypress or keydown event to + * interpret. + */ + function stopTypingOnEscapeKey( event ) { + const { keyCode } = event; + + if ( keyCode === ESCAPE || keyCode === TAB ) { + stopTyping(); + } } - } - lastMouseMove.current = { clientX, clientY }; - } + /** + * On selection change, unset typing flag if user has made an + * uncollapsed (shift) selection. + */ + function stopTypingOnSelectionUncollapse() { + const selection = defaultView.getSelection(); + const isCollapsed = + selection.rangeCount > 0 && + selection.getRangeAt( 0 ).collapsed; + + if ( ! isCollapsed ) { + stopTyping(); + } + } - /** - * On selection change, unset typing flag if user has made an uncollapsed - * (shift) selection. - */ - function stopTypingOnSelectionUncollapse( { target } ) { - const selection = target.defaultView.getSelection(); - const isCollapsed = - selection.rangeCount > 0 && selection.getRangeAt( 0 ).collapsed; + let lastClientX; + let lastClientY; + + /** + * On mouse move, unset typing flag if user has moved cursor. + * + * @param {MouseEvent} event Mousemove event. + */ + function stopTypingOnMouseMove( event ) { + const { clientX, clientY } = event; + + // We need to check that the mouse really moved because Safari + // triggers mousemove events when shift or ctrl are pressed. + if ( + lastClientX && + lastClientY && + ( lastClientX !== clientX || lastClientY !== clientY ) + ) { + stopTyping(); + } + + lastClientX = clientX; + lastClientY = clientY; + } - if ( ! isCollapsed ) { - stopTyping(); - } - } - - /** - * Unsets typing flag if user presses Escape while typing flag is active. - * - * @param {KeyboardEvent} event Keypress or keydown event to interpret. - */ - function stopTypingOnEscapeKey( event ) { - if ( - isTyping && - ( event.keyCode === ESCAPE || event.keyCode === TAB ) - ) { - stopTyping(); - } - } - - /** - * Handles a keypress or keydown event to infer intention to start typing. - * - * @param {KeyboardEvent} event Keypress or keydown event to interpret. - */ - function startTypingInTextField( event ) { - const { type, target } = event; - - // Abort early if already typing, or key press is incurred outside a - // text field (e.g. arrow-ing through toolbar buttons). - // Ignore typing if outside the current DOM container - if ( - isTyping || - ! isTextField( target ) || - ! typingContainer.current.contains( target ) - ) { - return; + element.addEventListener( 'focus', stopTypingOnNonTextField ); + element.addEventListener( 'keydown', stopTypingOnEscapeKey ); + ownerDocument.addEventListener( + 'selectionchange', + stopTypingOnSelectionUncollapse + ); + ownerDocument.addEventListener( + 'mousemove', + stopTypingOnMouseMove + ); + + return () => { + defaultView.clearTimeout( timerId ); + element.removeEventListener( + 'focus', + stopTypingOnNonTextField + ); + element.removeEventListener( 'keydown', stopTypingOnEscapeKey ); + ownerDocument.removeEventListener( + 'selectionchange', + stopTypingOnSelectionUncollapse + ); + ownerDocument.removeEventListener( + 'mousemove', + stopTypingOnMouseMove + ); + }; } - // Special-case keydown because certain keys do not emit a keypress - // event. Conversely avoid keydown as the canonical event since there - // are many keydown which are explicitly not targeted for typing. - if ( - type === 'keydown' && - ! isKeyDownEligibleForStartTyping( event ) - ) { - return; - } + /** + * Handles a keypress or keydown event to infer intention to start + * typing. + * + * @param {KeyboardEvent} event Keypress or keydown event to interpret. + */ + function startTypingInTextField( event ) { + const { type, target } = event; + + // Abort early if already typing, or key press is incurred outside a + // text field (e.g. arrow-ing through toolbar buttons). + // Ignore typing if outside the current DOM container + if ( ! isTextField( target ) || ! element.contains( target ) ) { + return; + } - startTyping(); - } - - /** - * Stops typing when focus transitions to a non-text field element. - * - * @param {FocusEvent} event Focus event. - */ - function stopTypingOnNonTextField( event ) { - const { target } = event; - - // Since focus to a non-text field via arrow key will trigger before - // the keydown event, wait until after current stack before evaluating - // whether typing is to be stopped. Otherwise, typing will re-start. - setSafeTimeout( () => { - if ( isTyping && ! isTextField( target ) ) { - stopTyping(); + // Special-case keydown because certain keys do not emit a keypress + // event. Conversely avoid keydown as the canonical event since + // there are many keydown which are explicitly not targeted for + // typing. + if ( + type === 'keydown' && + ! isKeyDownEligibleForStartTyping( event ) + ) { + return; } - } ); - } - - // Disable reason: This component is responsible for capturing bubbled - // keyboard events which are interpreted as typing intent. - - /* eslint-disable jsx-a11y/no-static-element-interactions */ - return ( -
- { children } -
- ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ + + startTyping(); + } + + element.addEventListener( 'keypress', startTypingInTextField ); + element.addEventListener( 'keydown', startTypingInTextField ); + + return () => { + element.removeEventListener( 'keypress', startTypingInTextField ); + element.removeEventListener( 'keydown', startTypingInTextField ); + }; + }, [ isTyping, startTyping, stopTyping ] ); +} + +function ObserveTyping( { children } ) { + const ref = useRef(); + useTypingObserver( ref ); + return
{ children }
; } /** * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/observe-typing/README.md */ -export default withSafeTimeout( ObserveTyping ); +export default ObserveTyping; diff --git a/packages/block-editor/src/components/plain-text/style.scss b/packages/block-editor/src/components/plain-text/style.scss index abcfaf5c8c1de4..0a070ac91e9e9c 100644 --- a/packages/block-editor/src/components/plain-text/style.scss +++ b/packages/block-editor/src/components/plain-text/style.scss @@ -1,4 +1,4 @@ -.block-editor .block-editor-plain-text { +.block-editor-plain-text { box-shadow: none; font-family: inherit; font-size: inherit; diff --git a/packages/block-editor/src/components/preview-options/style.scss b/packages/block-editor/src/components/preview-options/style.scss index 328032ad72b3b9..166764b35498f3 100644 --- a/packages/block-editor/src/components/preview-options/style.scss +++ b/packages/block-editor/src/components/preview-options/style.scss @@ -39,3 +39,30 @@ } } } + +// Reduced UI. +.edit-post-header.has-reduced-ui { + @include break-small() { + // Apply transition to first two buttons. + .edit-post-header__settings .editor-post-save-draft, + .edit-post-header__settings .editor-post-saved-state, + .edit-post-header__settings .block-editor-post-preview__button-toggle { + transition: opacity 0.1s linear; + @include reduce-motion("transition"); + } + + // Zero out opacity unless hovered. + &:not(:hover) { + .edit-post-header__settings .editor-post-save-draft, + .edit-post-header__settings .editor-post-saved-state, + .edit-post-header__settings .block-editor-post-preview__button-toggle { + opacity: 0; + } + + // ... or opened. + .edit-post-header__settings .block-editor-post-preview__button-toggle.is-opened { + opacity: 1; + } + } + } +} diff --git a/packages/block-editor/src/components/provider/test/use-block-sync.js b/packages/block-editor/src/components/provider/test/use-block-sync.js index 583d50f3547ad8..5d42ab62929610 100644 --- a/packages/block-editor/src/components/provider/test/use-block-sync.js +++ b/packages/block-editor/src/components/provider/test/use-block-sync.js @@ -97,8 +97,7 @@ describe( 'useBlockSync hook', () => { expect( onInput ).not.toHaveBeenCalled(); expect( replaceInnerBlocks ).toHaveBeenCalledWith( 'test', // It should use the given client ID. - fakeBlocks, // It should use the controlled blocks value. - false // It shoudl not update the selection state. + fakeBlocks // It should use the controlled blocks value. ); const testBlocks = [ @@ -119,11 +118,7 @@ describe( 'useBlockSync hook', () => { expect( onChange ).not.toHaveBeenCalled(); expect( onInput ).not.toHaveBeenCalled(); expect( resetBlocks ).not.toHaveBeenCalled(); - expect( replaceInnerBlocks ).toHaveBeenCalledWith( - 'test', - testBlocks, - false - ); + expect( replaceInnerBlocks ).toHaveBeenCalledWith( 'test', testBlocks ); } ); it( 'does not add the controlled blocks to the block-editor store if the store already contains them', async () => { @@ -315,7 +310,7 @@ describe( 'useBlockSync hook', () => { ); } ); - expect( replaceInnerBlocks ).toHaveBeenCalledWith( 'test', [], false ); + expect( replaceInnerBlocks ).toHaveBeenCalledWith( 'test', [] ); expect( onChange ).not.toHaveBeenCalled(); expect( onInput ).not.toHaveBeenCalled(); } ); diff --git a/packages/block-editor/src/components/provider/use-block-sync.js b/packages/block-editor/src/components/provider/use-block-sync.js index cf07c062b46b43..6a2f04c3f86b7c 100644 --- a/packages/block-editor/src/components/provider/use-block-sync.js +++ b/packages/block-editor/src/components/provider/use-block-sync.js @@ -96,7 +96,7 @@ export default function useBlockSync( { if ( clientId ) { setHasControlledInnerBlocks( clientId, true ); __unstableMarkNextChangeAsNotPersistent(); - replaceInnerBlocks( clientId, controlledBlocks, false ); + replaceInnerBlocks( clientId, controlledBlocks ); } else { resetBlocks( controlledBlocks ); } diff --git a/packages/block-editor/src/components/provider/with-registry-provider.js b/packages/block-editor/src/components/provider/with-registry-provider.js index 60109fea59f3c4..3b9f7b24c651e8 100644 --- a/packages/block-editor/src/components/provider/with-registry-provider.js +++ b/packages/block-editor/src/components/provider/with-registry-provider.js @@ -13,7 +13,6 @@ import { createHigherOrderComponent } from '@wordpress/compose'; * Internal dependencies */ import { storeConfig } from '../../store'; -import applyMiddlewares from '../../store/middlewares'; const withRegistryProvider = createHigherOrderComponent( ( WrappedComponent ) => { @@ -28,12 +27,10 @@ const withRegistryProvider = createHigherOrderComponent( const [ subRegistry, setSubRegistry ] = useState( null ); useEffect( () => { const newRegistry = createRegistry( {}, registry ); - const store = newRegistry.registerStore( + newRegistry.registerStore( 'core/block-editor', storeConfig ); - // This should be removed after the refactoring of the effects to controls. - applyMiddlewares( store ); setSubRegistry( newRegistry ); }, [ registry ] ); diff --git a/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap deleted file mode 100644 index 027e4ad5973ede..00000000000000 --- a/packages/block-editor/src/components/responsive-block-control/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Basic rendering should render with required props 1`] = `"
Padding

Toggle between using the same value for all screen sizes or using a unique value per screen size.

All is used here for testing purposes to ensure we have access to details about the device.

"`; diff --git a/packages/block-editor/src/components/responsive-block-control/test/index.js b/packages/block-editor/src/components/responsive-block-control/test/index.js index cc01fcd0533ceb..70b2425ea2886e 100644 --- a/packages/block-editor/src/components/responsive-block-control/test/index.js +++ b/packages/block-editor/src/components/responsive-block-control/test/index.js @@ -118,7 +118,6 @@ describe( 'Basic rendering', () => { expect( defaultControl ).not.toBeNull(); expect( toggleLabel ).not.toBeNull(); expect( toggleState ).toBe( true ); - expect( container.innerHTML ).toMatchSnapshot(); } ); it( 'should not render without valid legend', () => { diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index fb3651a8c3cb02..b3919feb052ef7 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -120,6 +120,7 @@ function RichTextWrapper( __unstableOnSplitMiddle: onSplitMiddle, identifier, preserveWhiteSpace, + __unstablePastePlainText: pastePlainText, __unstableEmbedURLOnPaste, __unstableDisableFormats: disableFormats, disableLineBreaks, @@ -408,6 +409,11 @@ function RichTextWrapper( const onPaste = useCallback( ( { value, onChange, html, plainText, files, activeFormats } ) => { + if ( pastePlainText ) { + onChange( insert( value, create( { text: plainText } ) ) ); + return; + } + // Only process file if no HTML is present. // Note: a pasted file may have the URL as plain text. if ( files && files.length && ! html ) { @@ -503,6 +509,7 @@ function RichTextWrapper( __unstableEmbedURLOnPaste, multiline, preserveWhiteSpace, + pastePlainText, ] ); @@ -632,6 +639,7 @@ function RichTextWrapper( record={ value } onChange={ onChange } isSelected={ nestedIsSelected } + contentRef={ ref } > { ( { listBoxId, activeId, onKeyDown } ) => ( { const onClick = () => { - const selectedBlockElement = getBlockDOMNode( selectedBlockClientId ); + const selectedBlockElement = getBlockDOMNode( + selectedBlockClientId, + document + ); selectedBlockElement.focus(); }; diff --git a/packages/block-editor/src/components/text-decoration-and-transform/index.js b/packages/block-editor/src/components/text-decoration-and-transform/index.js new file mode 100644 index 00000000000000..2d611b08b138ec --- /dev/null +++ b/packages/block-editor/src/components/text-decoration-and-transform/index.js @@ -0,0 +1,35 @@ +/** + * Internal dependencies + */ +import { + TextDecorationEdit, + useIsTextDecorationDisabled, +} from '../../hooks/text-decoration'; +import { + TextTransformEdit, + useIsTextTransformDisabled, +} from '../../hooks/text-transform'; + +/** + * Handles grouping related text decoration and text transform edit components + * so they can be laid out in a more flexible manner within the Typography + * InspectorControls panel. + * + * @param {Object} props Block props to be passed on to individual controls. + * @return {WPElement} Component containing text decoration or transform controls. + */ +export default function TextDecorationAndTransformEdit( props ) { + const decorationAvailable = ! useIsTextDecorationDisabled( props ); + const transformAvailable = ! useIsTextTransformDisabled( props ); + + if ( ! decorationAvailable && ! transformAvailable ) { + return null; + } + + return ( +
+ { decorationAvailable && } + { transformAvailable && } +
+ ); +} diff --git a/packages/block-editor/src/components/text-decoration-and-transform/style.scss b/packages/block-editor/src/components/text-decoration-and-transform/style.scss new file mode 100644 index 00000000000000..3f62e923036a4b --- /dev/null +++ b/packages/block-editor/src/components/text-decoration-and-transform/style.scss @@ -0,0 +1,3 @@ +.block-editor-text-decoration-and-transform { + display: flex; +} diff --git a/packages/block-editor/src/components/text-decoration-control/index.js b/packages/block-editor/src/components/text-decoration-control/index.js new file mode 100644 index 00000000000000..0385a5d6a943ec --- /dev/null +++ b/packages/block-editor/src/components/text-decoration-control/index.js @@ -0,0 +1,55 @@ +/** + * WordPress dependencies + */ +import { Button } from '@wordpress/components'; +import { formatStrikethrough, formatUnderline } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; + +const TEXT_DECORATIONS = [ + { + name: __( 'Underline' ), + value: 'underline', + icon: formatUnderline, + }, + { + name: __( 'Strikethrough' ), + value: 'line-through', + icon: formatStrikethrough, + }, +]; + +/** + * Control to facilitate text decoration selections. + * + * @param {Object} props Component props. + * @param {string} props.value Currently selected text decoration. + * @param {Function} props.onChange Handles change in text decoration selection. + * @return {WPElement} Text decoration control. + */ +export default function TextDecorationControl( { value, onChange } ) { + return ( +
+ { __( 'Decoration' ) } +
+ { TEXT_DECORATIONS.map( ( textDecoration ) => { + return ( +
+
+ ); +} diff --git a/packages/block-editor/src/components/text-decoration-control/style.scss b/packages/block-editor/src/components/text-decoration-control/style.scss new file mode 100644 index 00000000000000..f5d5848d3d3034 --- /dev/null +++ b/packages/block-editor/src/components/text-decoration-control/style.scss @@ -0,0 +1,18 @@ +.block-editor-text-decoration-control { + flex: 0 0 50%; + + legend { + margin-bottom: 8px; + } + + .block-editor-text-decoration-control__buttons { + display: inline-flex; + margin-bottom: 24px; + + .components-button.has-icon { + min-width: 24px; + padding: 0; + margin-right: 4px; + } + } +} diff --git a/packages/block-editor/src/components/text-transform-control/index.js b/packages/block-editor/src/components/text-transform-control/index.js new file mode 100644 index 00000000000000..6aeec3101256db --- /dev/null +++ b/packages/block-editor/src/components/text-transform-control/index.js @@ -0,0 +1,64 @@ +/** + * WordPress dependencies + */ +import { Button } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { + formatCapitalize, + formatLowercase, + formatUppercase, +} from '@wordpress/icons'; + +const TEXT_TRANSFORMS = [ + { + name: __( 'Uppercase' ), + value: 'uppercase', + icon: formatUppercase, + }, + { + name: __( 'Lowercase' ), + value: 'lowercase', + icon: formatLowercase, + }, + { + name: __( 'Capitalize' ), + value: 'capitalize', + icon: formatCapitalize, + }, +]; + +/** + * Control to facilitate text transform selections. + * + * @param {Object} props Component props. + * @param {string} props.value Currently selected text transform. + * @param {Function} props.onChange Handles change in text transform selection. + * @return {WPElement} Text transform control. + */ +export default function TextTransformControl( { value, onChange } ) { + return ( +
+ { __( 'Letter case' ) } +
+ { TEXT_TRANSFORMS.map( ( textTransform ) => { + return ( +
+
+ ); +} diff --git a/packages/block-editor/src/components/text-transform-control/style.scss b/packages/block-editor/src/components/text-transform-control/style.scss new file mode 100644 index 00000000000000..09280029a971aa --- /dev/null +++ b/packages/block-editor/src/components/text-transform-control/style.scss @@ -0,0 +1,18 @@ +.block-editor-text-transform-control { + flex: 0 0 50%; + + legend { + margin-bottom: 8px; + } + + .block-editor-text-transform-control__buttons { + display: inline-flex; + margin-bottom: 24px; + + .components-button.has-icon { + min-width: 24px; + padding: 0; + margin-right: 4px; + } + } +} diff --git a/packages/block-editor/src/components/tool-selector/style.scss b/packages/block-editor/src/components/tool-selector/style.scss index 2f6c1d018e572d..ad605ef037fe79 100644 --- a/packages/block-editor/src/components/tool-selector/style.scss +++ b/packages/block-editor/src/components/tool-selector/style.scss @@ -6,4 +6,5 @@ padding: $grid-unit-15 ($grid-unit-15 + $grid-unit-10); border-top: 1px solid $gray-300; color: $gray-700; + min-width: 280px; } diff --git a/packages/block-editor/src/components/typewriter/index.js b/packages/block-editor/src/components/typewriter/index.js index cfc8c8dd577c3a..522509f8e67561 100644 --- a/packages/block-editor/src/components/typewriter/index.js +++ b/packages/block-editor/src/components/typewriter/index.js @@ -1,286 +1,256 @@ /** * WordPress dependencies */ -import { Component, createRef } from '@wordpress/element'; +import { useEffect, useRef } from '@wordpress/element'; import { computeCaretRect, getScrollContainer } from '@wordpress/dom'; -import { withSelect } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; import { UP, DOWN, LEFT, RIGHT } from '@wordpress/keycodes'; -/** @typedef {import('@wordpress/element').WPSyntheticEvent} WPSyntheticEvent */ - const isIE = window.navigator.userAgent.indexOf( 'Trident' ) !== -1; const arrowKeyCodes = new Set( [ UP, DOWN, LEFT, RIGHT ] ); const initialTriggerPercentage = 0.75; -class Typewriter extends Component { - constructor() { - super( ...arguments ); +export function useTypewriter( ref ) { + const hasSelectedBlock = useSelect( ( select ) => + select( 'core/block-editor' ).hasSelectedBlock() + ); - this.ref = createRef(); - this.onKeyDown = this.onKeyDown.bind( this ); - this.addSelectionChangeListener = this.addSelectionChangeListener.bind( - this - ); - this.computeCaretRectOnSelectionChange = this.computeCaretRectOnSelectionChange.bind( - this - ); - this.maintainCaretPosition = this.maintainCaretPosition.bind( this ); - this.computeCaretRect = this.computeCaretRect.bind( this ); - this.onScrollResize = this.onScrollResize.bind( this ); - this.isSelectionEligibleForScroll = this.isSelectionEligibleForScroll.bind( - this - ); - this.getDocument = this.getDocument.bind( this ); - this.getWindow = this.getWindow.bind( this ); - } + useEffect( () => { + if ( ! hasSelectedBlock ) { + return; + } - componentDidMount() { - // When the user scrolls or resizes, the scroll position should be - // reset. - this.getWindow().addEventListener( - 'scroll', - this.onScrollResize, - true - ); - this.getWindow().addEventListener( - 'resize', - this.onScrollResize, - true - ); - } + const { ownerDocument } = ref.current; + const { defaultView } = ownerDocument; - componentWillUnmount() { - this.getWindow().removeEventListener( - 'scroll', - this.onScrollResize, - true - ); - this.getWindow().removeEventListener( - 'resize', - this.onScrollResize, - true - ); - this.getDocument().removeEventListener( - 'selectionchange', - this.computeCaretRectOnSelectionChange - ); + let scrollResizeRafId; + let onKeyDownRafId; - if ( this.onScrollResize.rafId ) { - this.getWindow().cancelAnimationFrame( this.onScrollResize.rafId ); - } + let caretRect; - if ( this.onKeyDown.rafId ) { - this.getWindow().cancelAnimationFrame( this.onKeyDown.rafId ); - } - } - - getDocument() { - return this.ref.current.ownerDocument; - } - - getWindow() { - return this.getDocument().defaultView; - } - - /** - * Resets the scroll position to be maintained. - */ - computeCaretRect() { - if ( this.isSelectionEligibleForScroll() ) { - this.caretRect = computeCaretRect( this.getWindow() ); - } - } - - /** - * Resets the scroll position to be maintained during a `selectionchange` - * event. Also removes the listener, so it acts as a one-time listener. - */ - computeCaretRectOnSelectionChange() { - this.getDocument().removeEventListener( - 'selectionchange', - this.computeCaretRectOnSelectionChange - ); - this.computeCaretRect(); - } + function onScrollResize() { + if ( scrollResizeRafId ) { + return; + } - onScrollResize() { - if ( this.onScrollResize.rafId ) { - return; + scrollResizeRafId = defaultView.requestAnimationFrame( () => { + computeCaretRectangle(); + scrollResizeRafId = null; + } ); } - this.onScrollResize.rafId = this.getWindow().requestAnimationFrame( - () => { - this.computeCaretRect(); - delete this.onScrollResize.rafId; + function onKeyDown( event ) { + // Ensure the any remaining request is cancelled. + if ( onKeyDownRafId ) { + defaultView.cancelAnimationFrame( onKeyDownRafId ); } - ); - } - - /** - * Checks if the current situation is elegible for scroll: - * - There should be one and only one block selected. - * - The component must contain the selection. - * - The active element must be contenteditable. - */ - isSelectionEligibleForScroll() { - return ( - this.props.selectedBlockClientId && - this.ref.current.contains( this.getDocument().activeElement ) && - this.getDocument().activeElement.isContentEditable - ); - } - isLastEditableNode() { - const editableNodes = this.ref.current.querySelectorAll( - '[contenteditable="true"]' - ); - const lastEditableNode = editableNodes[ editableNodes.length - 1 ]; - return lastEditableNode === this.getDocument().activeElement; - } - - /** - * Maintains the scroll position after a selection change caused by a - * keyboard event. - * - * @param {WPSyntheticEvent} event Synthetic keyboard event. - */ - maintainCaretPosition( { keyCode } ) { - if ( ! this.isSelectionEligibleForScroll() ) { - return; + // Use an animation frame for a smooth result. + onKeyDownRafId = defaultView.requestAnimationFrame( () => { + maintainCaretPosition( event ); + onKeyDownRafId = null; + } ); } - const currentCaretRect = computeCaretRect( this.getWindow() ); + /** + * Maintains the scroll position after a selection change caused by a + * keyboard event. + * + * @param {KeyboardEvent} event Keyboard event. + */ + function maintainCaretPosition( { keyCode } ) { + if ( ! isSelectionEligibleForScroll() ) { + return; + } - if ( ! currentCaretRect ) { - return; - } + const currentCaretRect = computeCaretRect( defaultView ); - // If for some reason there is no position set to be scrolled to, let - // this be the position to be scrolled to in the future. - if ( ! this.caretRect ) { - this.caretRect = currentCaretRect; - return; - } + if ( ! currentCaretRect ) { + return; + } - // Even though enabling the typewriter effect for arrow keys results in - // a pleasant experience, it may not be the case for everyone, so, for - // now, let's disable it. - if ( arrowKeyCodes.has( keyCode ) ) { - // Reset the caret position to maintain. - this.caretRect = currentCaretRect; - return; - } + // If for some reason there is no position set to be scrolled to, let + // this be the position to be scrolled to in the future. + if ( ! caretRect ) { + caretRect = currentCaretRect; + return; + } - const diff = currentCaretRect.top - this.caretRect.top; + // Even though enabling the typewriter effect for arrow keys results in + // a pleasant experience, it may not be the case for everyone, so, for + // now, let's disable it. + if ( arrowKeyCodes.has( keyCode ) ) { + // Reset the caret position to maintain. + caretRect = currentCaretRect; + return; + } - if ( diff === 0 ) { - return; - } + const diff = currentCaretRect.top - caretRect.top; - const scrollContainer = getScrollContainer( this.ref.current ); + if ( diff === 0 ) { + return; + } - // The page must be scrollable. - if ( ! scrollContainer ) { - return; + const scrollContainer = getScrollContainer( ref.current ); + + // The page must be scrollable. + if ( ! scrollContainer ) { + return; + } + + const windowScroll = scrollContainer === ownerDocument.body; + const scrollY = windowScroll + ? defaultView.scrollY + : scrollContainer.scrollTop; + const scrollContainerY = windowScroll + ? 0 + : scrollContainer.getBoundingClientRect().top; + const relativeScrollPosition = windowScroll + ? caretRect.top / defaultView.innerHeight + : ( caretRect.top - scrollContainerY ) / + ( defaultView.innerHeight - scrollContainerY ); + + // If the scroll position is at the start, the active editable element + // is the last one, and the caret is positioned within the initial + // trigger percentage of the page, do not scroll the page. + // The typewriter effect should not kick in until an empty page has been + // filled with the initial trigger percentage or the user scrolls + // intentionally down. + if ( + scrollY === 0 && + relativeScrollPosition < initialTriggerPercentage && + isLastEditableNode() + ) { + // Reset the caret position to maintain. + caretRect = currentCaretRect; + return; + } + + const scrollContainerHeight = windowScroll + ? defaultView.innerHeight + : scrollContainer.clientHeight; + + // Abort if the target scroll position would scroll the caret out of + // view. + if ( + // The caret is under the lower fold. + caretRect.top + caretRect.height > + scrollContainerY + scrollContainerHeight || + // The caret is above the upper fold. + caretRect.top < scrollContainerY + ) { + // Reset the caret position to maintain. + caretRect = currentCaretRect; + return; + } + + if ( windowScroll ) { + defaultView.scrollBy( 0, diff ); + } else { + scrollContainer.scrollTop += diff; + } } - const windowScroll = scrollContainer === this.getDocument().body; - const scrollY = windowScroll - ? this.getWindow().scrollY - : scrollContainer.scrollTop; - const scrollContainerY = windowScroll - ? 0 - : scrollContainer.getBoundingClientRect().top; - const relativeScrollPosition = windowScroll - ? this.caretRect.top / this.getWindow().innerHeight - : ( this.caretRect.top - scrollContainerY ) / - ( this.getWindow().innerHeight - scrollContainerY ); - - // If the scroll position is at the start, the active editable element - // is the last one, and the caret is positioned within the initial - // trigger percentage of the page, do not scroll the page. - // The typewriter effect should not kick in until an empty page has been - // filled with the initial trigger percentage or the user scrolls - // intentionally down. - if ( - scrollY === 0 && - relativeScrollPosition < initialTriggerPercentage && - this.isLastEditableNode() - ) { - // Reset the caret position to maintain. - this.caretRect = currentCaretRect; - return; + /** + * Adds a `selectionchange` listener to reset the scroll position to be + * maintained. + */ + function addSelectionChangeListener() { + ownerDocument.addEventListener( + 'selectionchange', + computeCaretRectOnSelectionChange + ); } - const scrollContainerHeight = windowScroll - ? this.getWindow().innerHeight - : scrollContainer.clientHeight; - - // Abort if the target scroll position would scroll the caret out of - // view. - if ( - // The caret is under the lower fold. - this.caretRect.top + this.caretRect.height > - scrollContainerY + scrollContainerHeight || - // The caret is above the upper fold. - this.caretRect.top < scrollContainerY - ) { - // Reset the caret position to maintain. - this.caretRect = currentCaretRect; - return; + /** + * Resets the scroll position to be maintained during a `selectionchange` + * event. Also removes the listener, so it acts as a one-time listener. + */ + function computeCaretRectOnSelectionChange() { + ownerDocument.removeEventListener( + 'selectionchange', + computeCaretRectOnSelectionChange + ); + computeCaretRectangle(); } - if ( windowScroll ) { - this.getWindow().scrollBy( 0, diff ); - } else { - scrollContainer.scrollTop += diff; + /** + * Resets the scroll position to be maintained. + */ + function computeCaretRectangle() { + if ( isSelectionEligibleForScroll() ) { + caretRect = computeCaretRect( defaultView ); + } } - } - - /** - * Adds a `selectionchange` listener to reset the scroll position to be - * maintained. - */ - addSelectionChangeListener() { - this.getDocument().addEventListener( - 'selectionchange', - this.computeCaretRectOnSelectionChange - ); - } - onKeyDown( event ) { - event.persist(); + /** + * Checks if the current situation is elegible for scroll: + * - There should be one and only one block selected. + * - The component must contain the selection. + * - The active element must be contenteditable. + */ + function isSelectionEligibleForScroll() { + return ( + ref.current.contains( ownerDocument.activeElement ) && + ownerDocument.activeElement.isContentEditable + ); + } - // Ensure the any remaining request is cancelled. - if ( this.onKeyDown.rafId ) { - this.getWindow().cancelAnimationFrame( this.onKeyDown.rafId ); + function isLastEditableNode() { + const editableNodes = ref.current.querySelectorAll( + '[contenteditable="true"]' + ); + const lastEditableNode = editableNodes[ editableNodes.length - 1 ]; + return lastEditableNode === ownerDocument.activeElement; } - // Use an animation frame for a smooth result. - this.onKeyDown.rafId = this.getWindow().requestAnimationFrame( () => { - this.maintainCaretPosition( event ); - delete this.onKeyDown.rafId; - } ); - } - - render() { - // Disable reason: Wrapper itself is non-interactive, but must capture - // bubbling events from children to determine focus transition intents. - /* eslint-disable jsx-a11y/no-static-element-interactions */ - return ( -
- { this.props.children } -
+ // When the user scrolls or resizes, the scroll position should be + // reset. + defaultView.addEventListener( 'scroll', onScrollResize, true ); + defaultView.addEventListener( 'resize', onScrollResize, true ); + + ref.current.addEventListener( 'keydown', onKeyDown ); + ref.current.addEventListener( 'keyup', maintainCaretPosition ); + ref.current.addEventListener( 'mousedown', addSelectionChangeListener ); + ref.current.addEventListener( + 'touchstart', + addSelectionChangeListener ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ - } + + return () => { + defaultView.removeEventListener( 'scroll', onScrollResize, true ); + defaultView.removeEventListener( 'resize', onScrollResize, true ); + + ref.current.removeEventListener( 'keydown', onKeyDown ); + ref.current.removeEventListener( 'keyup', maintainCaretPosition ); + ref.current.removeEventListener( + 'mousedown', + addSelectionChangeListener + ); + ref.current.removeEventListener( + 'touchstart', + addSelectionChangeListener + ); + + ownerDocument.removeEventListener( + 'selectionchange', + computeCaretRectOnSelectionChange + ); + + defaultView.cancelAnimationFrame( scrollResizeRafId ); + defaultView.cancelAnimationFrame( onKeyDownRafId ); + }; + }, [ hasSelectedBlock ] ); +} + +function Typewriter( { children } ) { + const ref = useRef(); + useTypewriter( ref ); + return ( +
+ { children } +
+ ); } /** @@ -290,12 +260,7 @@ class Typewriter extends Component { * * @type {WPComponent} */ -const TypewriterOrIEBypass = isIE - ? ( props ) => props.children - : withSelect( ( select ) => { - const { getSelectedBlockClientId } = select( 'core/block-editor' ); - return { selectedBlockClientId: getSelectedBlockClientId() }; - } )( Typewriter ); +const TypewriterOrIEBypass = isIE ? ( props ) => props.children : Typewriter; /** * Ensures that the text selection keeps the same vertical distance from the diff --git a/packages/block-editor/src/components/ungroup-button/index.native.js b/packages/block-editor/src/components/ungroup-button/index.native.js index e3056a5104e9e6..652a848b8cba42 100644 --- a/packages/block-editor/src/components/ungroup-button/index.native.js +++ b/packages/block-editor/src/components/ungroup-button/index.native.js @@ -6,6 +6,7 @@ import { noop } from 'lodash'; /** * WordPress dependencies */ +import { store as blocksStore } from '@wordpress/blocks'; import { Toolbar, ToolbarButton } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { withSelect, withDispatch } from '@wordpress/data'; @@ -37,7 +38,7 @@ export default compose( [ 'core/block-editor' ); - const { getGroupingBlockName } = select( 'core/blocks' ); + const { getGroupingBlockName } = select( blocksStore ); const selectedId = getSelectedBlockClientId(); const selectedBlock = getBlock( selectedId ); diff --git a/packages/block-editor/src/components/url-input/README.md b/packages/block-editor/src/components/url-input/README.md index d78a1cb09d56a8..b2837bb6dca430 100644 --- a/packages/block-editor/src/components/url-input/README.md +++ b/packages/block-editor/src/components/url-input/README.md @@ -129,12 +129,6 @@ Renders the URL input field used by the `URLInputButton` component. It can be us *Optional.* If this property is added, a label will be generated using label property as the content. -### `autoFocus: Boolean` - -*Optional.* By default, the input will gain focus when it is rendered, as typically it is displayed conditionally. For example when clicking on `URLInputButton` or editing a block. - -If you are not conditionally rendering this component set this property to `false`. - ### `className: String` *Optional.* Adds and optional class to the parent `div` that wraps the URLInput field and popover diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 26b0f98c81daf2..944c9694fda8fe 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -22,12 +22,6 @@ import { withInstanceId, withSafeTimeout, compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; import { isURL } from '@wordpress/url'; -// Since URLInput is rendered in the context of other inputs, but should be -// considered a separate modal node, prevent keyboard events from propagating -// as being considered from the input. -const stopEventPropagation = ( event ) => event.stopPropagation(); - -/* eslint-disable jsx-a11y/no-autofocus */ class URLInput extends Component { constructor( props ) { super( props ); @@ -398,7 +392,6 @@ class URLInput extends Component { placeholder = __( 'Paste URL or type to search' ), __experimentalRenderControl: renderControl, value = '', - autoFocus = true, } = this.props; const { @@ -420,12 +413,10 @@ class URLInput extends Component { const inputProps = { value, required: true, - autoFocus, className: 'block-editor-url-input__input', type: 'text', onChange: this.onChange, onFocus: this.onFocus, - onInput: stopEventPropagation, placeholder, onKeyDown: this.onKeyDown, role: 'combobox', @@ -545,7 +536,6 @@ class URLInput extends Component { return null; } } -/* eslint-enable jsx-a11y/no-autofocus */ /** * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/url-input/README.md diff --git a/packages/block-editor/src/components/url-popover/image-url-input-ui.js b/packages/block-editor/src/components/url-popover/image-url-input-ui.js index ec2fa36bb68b1e..660e0fbb558d40 100644 --- a/packages/block-editor/src/components/url-popover/image-url-input-ui.js +++ b/packages/block-editor/src/components/url-popover/image-url-input-ui.js @@ -18,7 +18,6 @@ import { SVG, Path, } from '@wordpress/components'; -import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; import { link as linkIcon, close } from '@wordpress/icons'; /** @@ -61,21 +60,6 @@ const ImageURLInputUI = ( { const autocompleteRef = useRef( null ); - const stopPropagation = ( event ) => { - event.stopPropagation(); - }; - - const stopPropagationRelevantKeys = ( event ) => { - if ( - [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( - event.keyCode - ) > -1 - ) { - // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. - event.stopPropagation(); - } - }; - const startEditLink = useCallback( () => { if ( linkDestination === LINK_DESTINATION_MEDIA || @@ -240,14 +224,10 @@ const ImageURLInputUI = ( { label={ __( 'Link Rel' ) } value={ removeNewTabRel( rel ) || '' } onChange={ onSetLinkRel } - onKeyPress={ stopPropagation } - onKeyDown={ stopPropagationRelevantKeys } /> @@ -299,8 +279,6 @@ const ImageURLInputUI = ( { className="block-editor-format-toolbar__link-container-content" value={ linkEditorValue } onChangeInputValue={ setUrlInput } - onKeyDown={ stopPropagationRelevantKeys } - onKeyPress={ stopPropagation } onSubmit={ onSubmitLinkChange() } autocompleteRef={ autocompleteRef } /> @@ -309,7 +287,6 @@ const ImageURLInputUI = ( { <> { + function onMouseDown( event ) { + // Only handle clicks on the canvas, not the content. + if ( event.target !== ref.current ) { + return; + } + + const focusableNodes = focus.focusable.find( ref.current ); + const target = findLast( focusableNodes, isTabbableTextField ); + + if ( ! target ) { + return; + } + + placeCaretAtHorizontalEdge( target, true ); + event.preventDefault(); + } + + ref.current.addEventListener( 'mousedown', onMouseDown ); + + return () => { + ref.current.addEventListener( 'mousedown', onMouseDown ); + }; + }, [] ); +} diff --git a/packages/block-editor/src/components/use-editor-feature/index.js b/packages/block-editor/src/components/use-editor-feature/index.js index c7afcb7db8bf2b..098ed231d8c186 100644 --- a/packages/block-editor/src/components/use-editor-feature/index.js +++ b/packages/block-editor/src/components/use-editor-feature/index.js @@ -1,11 +1,12 @@ /** * External dependencies */ -import { get } from 'lodash'; +import { get, isObject } from 'lodash'; /** * WordPress dependencies */ +import { store as blocksStore } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; /** @@ -47,6 +48,15 @@ const deprecatedFlags = { }, }; +function blockAttributesMatch( blockAttributes, attributes ) { + for ( const attribute in attributes ) { + if ( attributes[ attribute ] !== blockAttributes[ attribute ] ) { + return false; + } + } + return true; +} + /** * Hook that retrieves the setting for the given editor feature. * It works with nested objects using by finding the value at path. @@ -61,19 +71,36 @@ const deprecatedFlags = { * ``` */ export default function useEditorFeature( featurePath ) { - const { name: blockName } = useBlockEditContext(); + const { name: blockName, clientId } = useBlockEditContext(); const setting = useSelect( ( select ) => { - const settings = select( 'core/block-editor' ).getSettings(); + const { getBlockAttributes, getSettings } = select( + 'core/block-editor' + ); + const settings = getSettings(); + const blockType = select( blocksStore ).getBlockType( blockName ); + + let context = blockName; + const selectors = get( blockType, [ + 'supports', + '__experimentalSelector', + ] ); + if ( isObject( selectors ) ) { + const blockAttributes = getBlockAttributes( clientId ) || {}; + for ( const contextSelector in selectors ) { + const { attributes } = selectors[ contextSelector ]; + if ( blockAttributesMatch( blockAttributes, attributes ) ) { + context = contextSelector; + break; + } + } + } // 1 - Use __experimental features, if available. // We cascade to the global value if the block one is not available. - // - // TODO: make it work for blocks that define multiple selectors - // such as core/heading or core/post-title. const globalPath = `__experimentalFeatures.global.${ featurePath }`; - const blockPath = `__experimentalFeatures.${ blockName }.${ featurePath }`; + const blockPath = `__experimentalFeatures.${ context }.${ featurePath }`; const experimentalFeaturesResult = get( settings, blockPath ) ?? get( settings, globalPath ); if ( experimentalFeaturesResult !== undefined ) { @@ -94,7 +121,7 @@ export default function useEditorFeature( featurePath ) { // To remove when __experimentalFeatures are ported to core. return featurePath === 'typography.dropCap' ? true : undefined; }, - [ blockName, featurePath ] + [ blockName, clientId, featurePath ] ); return setting; diff --git a/packages/block-editor/src/components/use-on-block-drop/index.js b/packages/block-editor/src/components/use-on-block-drop/index.js index eda98177563ef9..6f9cc801599f74 100644 --- a/packages/block-editor/src/components/use-on-block-drop/index.js +++ b/packages/block-editor/src/components/use-on-block-drop/index.js @@ -23,6 +23,7 @@ export function parseDropEvent( event ) { srcClientIds: null, srcIndex: null, type: null, + blocks: null, }; if ( ! event.dataTransfer ) { @@ -44,12 +45,13 @@ export function parseDropEvent( event ) { /** * A function that returns an event handler function for block drop events. * - * @param {string} targetRootClientId The root client id where the block(s) will be inserted. - * @param {number} targetBlockIndex The index where the block(s) will be inserted. + * @param {string} targetRootClientId The root client id where the block(s) will be inserted. + * @param {number} targetBlockIndex The index where the block(s) will be inserted. * @param {Function} getBlockIndex A function that gets the index of a block. * @param {Function} getClientIdsOfDescendants A function that gets the client ids of descendant blocks. * @param {Function} moveBlocksToPosition A function that moves blocks. - * + * @param {Function} insertBlocks A function that inserts blocks. + * @param {Function} clearSelectedBlock A function that clears block selection. * @return {Function} The event handler for a block drop event. */ export function onBlockDrop( @@ -57,62 +59,69 @@ export function onBlockDrop( targetBlockIndex, getBlockIndex, getClientIdsOfDescendants, - moveBlocksToPosition + moveBlocksToPosition, + insertBlocks, + clearSelectedBlock ) { return ( event ) => { const { srcRootClientId: sourceRootClientId, srcClientIds: sourceClientIds, type: dropType, + blocks, } = parseDropEvent( event ); - // If the user isn't dropping a block, return early. - if ( dropType !== 'block' ) { - return; + // If the user is inserting a block + if ( dropType === 'inserter' ) { + clearSelectedBlock(); + insertBlocks( blocks, targetBlockIndex, targetRootClientId, false ); } - const sourceBlockIndex = getBlockIndex( - sourceClientIds[ 0 ], - sourceRootClientId - ); - - // If the user is dropping to the same position, return early. - if ( - sourceRootClientId === targetRootClientId && - sourceBlockIndex === targetBlockIndex - ) { - return; - } + // If the user is moving a block + if ( dropType === 'block' ) { + const sourceBlockIndex = getBlockIndex( + sourceClientIds[ 0 ], + sourceRootClientId + ); - // If the user is attempting to drop a block within its own - // nested blocks, return early as this would create infinite - // recursion. - if ( - sourceClientIds.includes( targetRootClientId ) || - getClientIdsOfDescendants( sourceClientIds ).some( - ( id ) => id === targetRootClientId - ) - ) { - return; + // If the user is dropping to the same position, return early. + if ( + sourceRootClientId === targetRootClientId && + sourceBlockIndex === targetBlockIndex + ) { + return; + } + + // If the user is attempting to drop a block within its own + // nested blocks, return early as this would create infinite + // recursion. + if ( + sourceClientIds.includes( targetRootClientId ) || + getClientIdsOfDescendants( sourceClientIds ).some( + ( id ) => id === targetRootClientId + ) + ) { + return; + } + + const isAtSameLevel = sourceRootClientId === targetRootClientId; + const draggedBlockCount = sourceClientIds.length; + + // If the block is kept at the same level and moved downwards, + // subtract to take into account that the blocks being dragged + // were removed from the block list above the insertion point. + const insertIndex = + isAtSameLevel && sourceBlockIndex < targetBlockIndex + ? targetBlockIndex - draggedBlockCount + : targetBlockIndex; + + moveBlocksToPosition( + sourceClientIds, + sourceRootClientId, + targetRootClientId, + insertIndex + ); } - - const isAtSameLevel = sourceRootClientId === targetRootClientId; - const draggedBlockCount = sourceClientIds.length; - - // If the block is kept at the same level and moved downwards, - // subtract to take into account that the blocks being dragged - // were removed from the block list above the insertion point. - const insertIndex = - isAtSameLevel && sourceBlockIndex < targetBlockIndex - ? targetBlockIndex - draggedBlockCount - : targetBlockIndex; - - moveBlocksToPosition( - sourceClientIds, - sourceRootClientId, - targetRootClientId, - insertIndex - ); }; } @@ -209,6 +218,7 @@ export default function useOnBlockDrop( targetRootClientId, targetBlockIndex ) { insertBlocks, moveBlocksToPosition, updateBlockAttributes, + clearSelectedBlock, } = useDispatch( 'core/block-editor' ); return { @@ -217,7 +227,9 @@ export default function useOnBlockDrop( targetRootClientId, targetBlockIndex ) { targetBlockIndex, getBlockIndex, getClientIdsOfDescendants, - moveBlocksToPosition + moveBlocksToPosition, + insertBlocks, + clearSelectedBlock ), onFilesDrop: onFilesDrop( targetRootClientId, diff --git a/packages/block-editor/src/components/use-on-block-drop/test/index.js b/packages/block-editor/src/components/use-on-block-drop/test/index.js index beae8362fbfcb3..964a7cab74b06d 100644 --- a/packages/block-editor/src/components/use-on-block-drop/test/index.js +++ b/packages/block-editor/src/components/use-on-block-drop/test/index.js @@ -21,6 +21,7 @@ jest.mock( '@wordpress/blocks/src/api/raw-handling', () => ( { describe( 'parseDropEvent', () => { it( 'converts an event dataTransfer property from JSON to an object', () => { const rawDataTransfer = { + blocks: null, srcRootClientId: '123', srcClientIds: [ 'abc' ], srcIndex: 1, @@ -53,12 +54,14 @@ describe( 'parseDropEvent', () => { expect( parseDropEvent( event ) ).toEqual( { srcRootClientId: null, srcIndex: null, + blocks: null, ...rawDataTransfer, } ); } ); it( 'returns an object with null values if the event dataTransfer can not be parsed', () => { const expected = { + blocks: null, srcRootClientId: null, srcClientIds: null, srcIndex: null, @@ -77,6 +80,7 @@ describe( 'parseDropEvent', () => { it( 'returns an object with null values if the event has no dataTransfer property', () => { const expected = { + blocks: null, srcRootClientId: null, srcClientIds: null, srcIndex: null, diff --git a/packages/block-editor/src/components/writing-flow/focus-capture.js b/packages/block-editor/src/components/writing-flow/focus-capture.js index 8440a3097e0428..15bdb63d441d9c 100644 --- a/packages/block-editor/src/components/writing-flow/focus-capture.js +++ b/packages/block-editor/src/components/writing-flow/focus-capture.js @@ -80,7 +80,10 @@ const FocusCapture = forwardRef( // If there is a selected block, move focus to the first or last // tabbable element depending on the direction. - const wrapper = getBlockDOMNode( selectedClientId ); + const wrapper = getBlockDOMNode( + selectedClientId, + ref.current.ownerDocument + ); if ( isReverse ) { const tabbables = focus.tabbable.find( wrapper ); diff --git a/packages/block-editor/src/components/writing-flow/index.js b/packages/block-editor/src/components/writing-flow/index.js index f761e14eee0821..e9cc4132ce79f6 100644 --- a/packages/block-editor/src/components/writing-flow/index.js +++ b/packages/block-editor/src/components/writing-flow/index.js @@ -1,13 +1,13 @@ /** * External dependencies */ -import { overEvery, find, findLast, reverse, first, last } from 'lodash'; +import { find, reverse, first, last } from 'lodash'; import classnames from 'classnames'; /** * WordPress dependencies */ -import { useRef, useEffect, useState } from '@wordpress/element'; +import { useRef, useEffect, createContext } from '@wordpress/element'; import { computeCaretRect, focus, @@ -26,8 +26,6 @@ import { TAB, isKeyboardEvent, ESCAPE, - ENTER, - SPACE, } from '@wordpress/keycodes'; import { useSelect, useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; @@ -44,24 +42,14 @@ import { getBlockClientId, } from '../../utils/dom'; import FocusCapture from './focus-capture'; +import useMultiSelection from './use-multi-selection'; + +export const SelectionStart = createContext(); function getComputedStyle( node ) { return node.ownerDocument.defaultView.getComputedStyle( node ); } -/** - * Given an element, returns true if the element is a tabbable text field, or - * false otherwise. - * - * @param {Element} element Element to test. - * - * @return {boolean} Whether element is a tabbable text field. - */ -const isTabbableTextField = overEvery( [ - isTextField, - focus.tabbable.isTabbableIndex, -] ); - /** * Returns true if the element should consider edge navigation upon a keyboard * event of the given directional key code, or false otherwise. @@ -203,12 +191,6 @@ function selector( select ) { hasMultiSelection, getBlockOrder, isNavigationMode, - hasBlockMovingClientId, - getBlockIndex, - getBlockRootClientId, - getClientIdsOfDescendants, - canInsertBlockType, - getBlockName, isSelectionEnabled, getBlockSelectionStart, isMultiSelecting, @@ -218,6 +200,7 @@ function selector( select ) { const selectedBlockClientId = getSelectedBlockClientId(); const selectionStartClientId = getMultiSelectedBlocksStartClientId(); const selectionEndClientId = getMultiSelectedBlocksEndClientId(); + const blocks = getBlockOrder(); return { selectedBlockClientId, @@ -231,14 +214,9 @@ function selector( select ) { selectedFirstClientId: getFirstMultiSelectedBlockClientId(), selectedLastClientId: getLastMultiSelectedBlockClientId(), hasMultiSelection: hasMultiSelection(), - blocks: getBlockOrder(), + firstBlock: first( blocks ), + lastBlock: last( blocks ), isNavigationMode: isNavigationMode(), - hasBlockMovingClientId, - getBlockIndex, - getBlockRootClientId, - getClientIdsOfDescendants, - canInsertBlockType, - getBlockName, isSelectionEnabled: isSelectionEnabled(), blockSelectionStart: getBlockSelectionStart(), isMultiSelecting: isMultiSelecting(), @@ -270,6 +248,8 @@ export default function WritingFlow( { children } ) { // browser behaviour across blocks. const verticalRect = useRef(); + const onSelectionStart = useMultiSelection( container ); + const { selectedBlockClientId, selectionStartClientId, @@ -278,55 +258,33 @@ export default function WritingFlow( { children } ) { selectedFirstClientId, selectedLastClientId, hasMultiSelection, - blocks, + firstBlock, + lastBlock, isNavigationMode, - hasBlockMovingClientId, isSelectionEnabled, blockSelectionStart, isMultiSelecting, - getBlockIndex, - getBlockRootClientId, - getClientIdsOfDescendants, - canInsertBlockType, - getBlockName, keepCaretInsideBlock, } = useSelect( selector, [] ); - const { - multiSelect, - selectBlock, - clearSelectedBlock, - setNavigationMode, - setBlockMovingClientId, - moveBlockToPosition, - } = useDispatch( 'core/block-editor' ); - - const [ canInsertMovingBlock, setCanInsertMovingBlock ] = useState( false ); + const { multiSelect, selectBlock, setNavigationMode } = useDispatch( + 'core/block-editor' + ); function onMouseDown( event ) { verticalRect.current = null; + const { ownerDocument } = event.target; + // Clicking inside a selected block should exit navigation mode and block moving mode. if ( isNavigationMode && selectedBlockClientId && isInsideRootBlock( - getBlockDOMNode( selectedBlockClientId ), + getBlockDOMNode( selectedBlockClientId, ownerDocument ), event.target ) ) { setNavigationMode( false ); - setBlockMovingClientId( null ); - } else if ( - isNavigationMode && - hasBlockMovingClientId() && - getBlockClientId( event.target ) - ) { - setCanInsertMovingBlock( - canInsertBlockType( - getBlockName( hasBlockMovingClientId() ), - getBlockRootClientId( getBlockClientId( event.target ) ) - ) - ); } // Multi-select blocks when Shift+clicking. @@ -401,14 +359,25 @@ export default function WritingFlow( { children } ) { function onKeyDown( event ) { const { keyCode, target } = event; + + // Handle only if the event occurred within the same DOM hierarchy as + // the rendered container. This is used to distinguish between events + // which bubble through React's virtual event system from those which + // strictly occur in the DOM created by the component. + // + // The implication here is: If it's not desirable for a bubbled event to + // be considered by WritingFlow, it can be avoided by rendering to a + // distinct place in the DOM (e.g. using Slot/Fill). + if ( ! container.current.contains( target ) ) { + return; + } + const isUp = keyCode === UP; const isDown = keyCode === DOWN; const isLeft = keyCode === LEFT; const isRight = keyCode === RIGHT; const isTab = keyCode === TAB; const isEscape = keyCode === ESCAPE; - const isEnter = keyCode === ENTER; - const isSpace = keyCode === SPACE; const isReverse = isUp || isLeft; const isHorizontal = isLeft || isRight; const isVertical = isUp || isDown; @@ -417,97 +386,8 @@ export default function WritingFlow( { children } ) { const hasModifier = isShift || event.ctrlKey || event.altKey || event.metaKey; const isNavEdge = isVertical ? isVerticalEdge : isHorizontalEdge; - - // In navigation mode, tab and arrows navigate from block to block. - if ( isNavigationMode ) { - const navigateUp = ( isTab && isShift ) || isUp; - const navigateDown = ( isTab && ! isShift ) || isDown; - // Move out of current nesting level (no effect if at root level). - const navigateOut = isLeft; - // Move into next nesting level (no effect if the current block has no innerBlocks). - const navigateIn = isRight; - - let focusedBlockUid; - if ( navigateUp ) { - focusedBlockUid = selectionBeforeEndClientId; - } else if ( navigateDown ) { - focusedBlockUid = selectionAfterEndClientId; - } else if ( navigateOut ) { - focusedBlockUid = - getBlockRootClientId( selectedBlockClientId ) ?? - selectedBlockClientId; - } else if ( navigateIn ) { - focusedBlockUid = - getClientIdsOfDescendants( [ - selectedBlockClientId, - ] )[ 0 ] ?? selectedBlockClientId; - } - const startingBlockClientId = hasBlockMovingClientId(); - - if ( startingBlockClientId && focusedBlockUid ) { - setCanInsertMovingBlock( - canInsertBlockType( - getBlockName( startingBlockClientId ), - getBlockRootClientId( focusedBlockUid ) - ) - ); - } - if ( isEscape && startingBlockClientId ) { - setBlockMovingClientId( null ); - setCanInsertMovingBlock( false ); - } - if ( ( isEnter || isSpace ) && startingBlockClientId ) { - const sourceRoot = getBlockRootClientId( - startingBlockClientId - ); - const destRoot = getBlockRootClientId( selectedBlockClientId ); - const sourceBlockIndex = getBlockIndex( - startingBlockClientId, - sourceRoot - ); - let destinationBlockIndex = getBlockIndex( - selectedBlockClientId, - destRoot - ); - if ( - sourceBlockIndex < destinationBlockIndex && - sourceRoot === destRoot - ) { - destinationBlockIndex -= 1; - } - moveBlockToPosition( - startingBlockClientId, - sourceRoot, - destRoot, - destinationBlockIndex - ); - selectBlock( startingBlockClientId ); - setBlockMovingClientId( null ); - } - if ( navigateDown || navigateUp || navigateOut || navigateIn ) { - if ( focusedBlockUid ) { - event.preventDefault(); - selectBlock( focusedBlockUid ); - } else if ( isTab && selectedBlockClientId ) { - const wrapper = getBlockDOMNode( selectedBlockClientId ); - let nextTabbable; - - if ( navigateDown ) { - nextTabbable = focus.tabbable.findNext( wrapper ); - } else { - nextTabbable = focus.tabbable.findPrevious( wrapper ); - } - - if ( nextTabbable ) { - event.preventDefault(); - nextTabbable.focus(); - clearSelectedBlock(); - } - } - } - - return; - } + const { ownerDocument } = container.current; + const { defaultView } = ownerDocument; // In Edit mode, Tab should focus the first tabbable element after the // content, which is normally the sidebar (with block controls) and @@ -517,7 +397,10 @@ export default function WritingFlow( { children } ) { // Navigation mode (press Esc), to navigate through blocks. if ( selectedBlockClientId ) { if ( isTab ) { - const wrapper = getBlockDOMNode( selectedBlockClientId ); + const wrapper = getBlockDOMNode( + selectedBlockClientId, + ownerDocument + ); if ( isShift ) { if ( target === wrapper ) { @@ -542,26 +425,8 @@ export default function WritingFlow( { children } ) { } else if ( isEscape ) { setNavigationMode( true ); } - } else if ( - hasMultiSelection && - isTab && - target === multiSelectionContainer.current - ) { - // See comment above. - noCapture.current = true; - - if ( isShift ) { - focusCaptureBeforeRef.current.focus(); - } else { - focusCaptureAfterRef.current.focus(); - } - - return; } - const { ownerDocument } = container.current; - const { defaultView } = ownerDocument; - // When presing any key other than up or down, the initial vertical // position must ALWAYS be reset. The vertical position is saved so it // can be restored as well as possible on sebsequent vertical arrow key @@ -594,7 +459,7 @@ export default function WritingFlow( { children } ) { ? entirelySelected.current : isEntirelySelected( target ) ) { - multiSelect( first( blocks ), last( blocks ) ); + multiSelect( firstBlock, lastBlock ); event.preventDefault(); } @@ -628,19 +493,14 @@ export default function WritingFlow( { children } ) { // Ensure that there is a target block. ( ( isReverse && selectionBeforeEndClientId ) || ( ! isReverse && selectionAfterEndClientId ) ) && - ( hasMultiSelection || - ( isTabbableEdge( target, isReverse ) && - isNavEdge( target, isReverse ) ) ) + isTabbableEdge( target, isReverse ) && + isNavEdge( target, isReverse ) ) { // Shift key is down, and there is multi selection or we're at // the end of the current block. expandSelection( isReverse ); event.preventDefault(); } - } else if ( hasMultiSelection ) { - // Moving from block multi-selection to single block selection - moveSelection( isReverse ); - event.preventDefault(); } else if ( isVertical && isVerticalEdge( target, isReverse ) && @@ -677,11 +537,32 @@ export default function WritingFlow( { children } ) { } } - function focusLastTextField() { - const focusableNodes = focus.focusable.find( container.current ); - const target = findLast( focusableNodes, isTabbableTextField ); - if ( target ) { - placeCaretAtHorizontalEdge( target, true ); + function onMultiSelectKeyDown( event ) { + const { keyCode, shiftKey } = event; + const isUp = keyCode === UP; + const isDown = keyCode === DOWN; + const isLeft = keyCode === LEFT; + const isRight = keyCode === RIGHT; + const isReverse = isUp || isLeft; + const isHorizontal = isLeft || isRight; + const isVertical = isUp || isDown; + const isNav = isHorizontal || isVertical; + + if ( keyCode === TAB ) { + // Disable focus capturing on the focus capture element, so it + // doesn't refocus this element and so it allows default behaviour + // (moving focus to the next tabbable element). + noCapture.current = true; + + if ( shiftKey ) { + focusCaptureBeforeRef.current.focus(); + } else { + focusCaptureAfterRef.current.focus(); + } + } else if ( isNav ) { + const action = shiftKey ? expandSelection : moveSelection; + action( isReverse ); + event.preventDefault(); } } @@ -693,15 +574,13 @@ export default function WritingFlow( { children } ) { const className = classnames( 'block-editor-writing-flow', { 'is-navigate-mode': isNavigationMode, - 'is-block-moving-mode': !! hasBlockMovingClientId(), - 'can-insert-moving-block': canInsertMovingBlock, } ); // Disable reason: Wrapper itself is non-interactive, but must capture // bubbling events from children to determine focus transition intents. /* eslint-disable jsx-a11y/no-static-element-interactions */ return ( -
+ +
-
{ children }
-
-
+ ); /* eslint-enable jsx-a11y/no-static-element-interactions */ } diff --git a/packages/block-editor/src/components/writing-flow/style.scss b/packages/block-editor/src/components/writing-flow/style.scss deleted file mode 100644 index 422b378f2e8e2e..00000000000000 --- a/packages/block-editor/src/components/writing-flow/style.scss +++ /dev/null @@ -1,8 +0,0 @@ -.block-editor-writing-flow { - display: flex; - flex-direction: column; -} - -.block-editor-writing-flow__click-redirect { - cursor: text; -} diff --git a/packages/block-editor/src/components/block-list/use-multi-selection.js b/packages/block-editor/src/components/writing-flow/use-multi-selection.js similarity index 97% rename from packages/block-editor/src/components/block-list/use-multi-selection.js rename to packages/block-editor/src/components/writing-flow/use-multi-selection.js index 696ed2f55c5396..c3a4821d4050ac 100644 --- a/packages/block-editor/src/components/block-list/use-multi-selection.js +++ b/packages/block-editor/src/components/writing-flow/use-multi-selection.js @@ -102,7 +102,10 @@ export default function useMultiSelection( ref ) { const selection = defaultView.getSelection(); if ( selection.rangeCount && ! selection.isCollapsed ) { - const blockNode = getBlockDOMNode( selectedBlockClientId ); + const blockNode = getBlockDOMNode( + selectedBlockClientId, + ownerDocument + ); const { startContainer, endContainer } = selection.getRangeAt( 0 ); @@ -129,8 +132,8 @@ export default function useMultiSelection( ref ) { const start = multiSelectedBlockClientIds[ 0 ]; const end = multiSelectedBlockClientIds[ length - 1 ]; - let startNode = getBlockDOMNode( start ); - let endNode = getBlockDOMNode( end ); + let startNode = getBlockDOMNode( start, ownerDocument ); + let endNode = getBlockDOMNode( end, ownerDocument ); const selection = defaultView.getSelection(); const range = ownerDocument.createRange(); diff --git a/packages/block-editor/src/hooks/align.js b/packages/block-editor/src/hooks/align.js index 9d4e66d6556c06..67b8e803673336 100644 --- a/packages/block-editor/src/hooks/align.js +++ b/packages/block-editor/src/hooks/align.js @@ -7,7 +7,6 @@ import { has, without } from 'lodash'; /** * WordPress dependencies */ -import { createContext, useContext } from '@wordpress/element'; import { createHigherOrderComponent } from '@wordpress/compose'; import { addFilter } from '@wordpress/hooks'; import { @@ -59,7 +58,9 @@ export function getValidAlignments( ) { let validAlignments; if ( Array.isArray( blockAlign ) ) { - validAlignments = blockAlign; + validAlignments = ALL_ALIGNMENTS.filter( ( value ) => + blockAlign.includes( value ) + ); } else if ( blockAlign === true ) { // `true` includes all alignments... validAlignments = ALL_ALIGNMENTS; @@ -104,13 +105,6 @@ export function addAttribute( settings ) { return settings; } -const AlignmentHookSettings = createContext( {} ); - -/** - * Allows to pass additional settings to the alignment hook. - */ -export const AlignmentHookSettingsProvider = AlignmentHookSettings.Provider; - /** * Override the default edit UI to include new toolbar controls for block * alignment, if block defines support. @@ -120,17 +114,15 @@ export const AlignmentHookSettingsProvider = AlignmentHookSettings.Provider; */ export const withToolbarControls = createHigherOrderComponent( ( BlockEdit ) => ( props ) => { - const { isEmbedButton } = useContext( AlignmentHookSettings ); const { name: blockName } = props; // Compute valid alignments without taking into account, - // if the theme supports wide alignments or not. - // BlockAlignmentToolbar takes into account the theme support. - const validAlignments = isEmbedButton - ? [] - : getValidAlignments( - getBlockSupport( blockName, 'align' ), - hasBlockSupport( blockName, 'alignWide', true ) - ); + // if the theme supports wide alignments or not + // and without checking the layout for availble alignments. + // BlockAlignmentToolbar takes both of these into account. + const validAlignments = getValidAlignments( + getBlockSupport( blockName, 'align' ), + hasBlockSupport( blockName, 'alignWide', true ) + ); const updateAlignment = ( nextAlign ) => { if ( ! nextAlign ) { diff --git a/packages/block-editor/src/hooks/align.native.js b/packages/block-editor/src/hooks/align.native.js index 8fb3f6e2be93ca..ecdc88daae0ae5 100644 --- a/packages/block-editor/src/hooks/align.native.js +++ b/packages/block-editor/src/hooks/align.native.js @@ -12,7 +12,7 @@ import { WIDE_ALIGNMENTS } from '@wordpress/components'; const ALIGNMENTS = [ 'left', 'center', 'right' ]; -export { AlignmentHookSettingsProvider } from './align.js'; +export * from './align.js'; // Used to filter out blocks that don't support wide/full alignment on mobile addFilter( diff --git a/packages/block-editor/src/hooks/border-radius.js b/packages/block-editor/src/hooks/border-radius.js new file mode 100644 index 00000000000000..f9d90b444b640c --- /dev/null +++ b/packages/block-editor/src/hooks/border-radius.js @@ -0,0 +1,79 @@ +/** + * WordPress dependencies + */ +import { getBlockSupport } from '@wordpress/blocks'; +import { RangeControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import useEditorFeature from '../components/use-editor-feature'; +import { BORDER_SUPPORT_KEY } from './border'; +import { cleanEmptyObject } from './utils'; + +const MIN_BORDER_RADIUS_VALUE = 0; +const MAX_BORDER_RADIUS_VALUE = 50; + +/** + * Inspector control panel containing the border radius related configuration. + * + * @param {Object} props Block properties. + * @return {WPElement} Border radius edit element. + */ +export function BorderRadiusEdit( props ) { + const { + attributes: { style }, + setAttributes, + } = props; + + if ( useIsBorderRadiusDisabled( props ) ) { + return null; + } + + const onChange = ( newRadius ) => { + const newStyle = { + ...style, + border: { + ...style?.border, + radius: newRadius, + }, + }; + + setAttributes( { style: cleanEmptyObject( newStyle ) } ); + }; + + return ( + + ); +} + +/** + * Determines if there is border radius support. + * + * @param {string|Object} blockType Block name or Block Type object. + * @return {boolean} Whether there is support. + */ +export function hasBorderRadiusSupport( blockType ) { + const support = getBlockSupport( blockType, BORDER_SUPPORT_KEY ); + return true === support || ( support && !! support.radius ); +} + +/** + * Custom hook that checks if border radius settings have been disabled. + * + * @param {string} name The name of the block. + * @return {boolean} Whether border radius setting is disabled. + */ +export function useIsBorderRadiusDisabled( { name: blockName } = {} ) { + const isDisabled = ! useEditorFeature( 'border.customRadius' ); + return ! hasBorderRadiusSupport( blockName ) || isDisabled; +} diff --git a/packages/block-editor/src/hooks/border.js b/packages/block-editor/src/hooks/border.js new file mode 100644 index 00000000000000..228abd4083bfa5 --- /dev/null +++ b/packages/block-editor/src/hooks/border.js @@ -0,0 +1,67 @@ +/** + * WordPress dependencies + */ +import { getBlockSupport } from '@wordpress/blocks'; +import { PanelBody } from '@wordpress/components'; +import { Platform } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import InspectorControls from '../components/inspector-controls'; +import { BorderRadiusEdit, useIsBorderRadiusDisabled } from './border-radius'; + +export const BORDER_SUPPORT_KEY = '__experimentalBorder'; + +export function BorderPanel( props ) { + const isDisabled = useIsBorderDisabled( props ); + const isSupported = hasBorderSupport( props.name ); + + if ( isDisabled || ! isSupported ) { + return null; + } + + return ( + + + + + + ); +} + +/** + * Determine whether there is block support for borders. + * + * @param {string} blockName Block name. + * @return {boolean} Whether there is support. + */ +export function hasBorderSupport( blockName ) { + if ( Platform.OS !== 'web' ) { + return false; + } + + const support = getBlockSupport( blockName, BORDER_SUPPORT_KEY ); + + // Further border properties to be added in future iterations. + // e.g. support && ( support.radius || support.width || support.style ) + return true === support || ( support && support.radius ); +} + +/** + * Determines whether there is any block support for borders e.g. border radius, + * style, width etc. + * + * @param {Object} props Block properties. + * @return {boolean} If border support is completely disabled. + */ +const useIsBorderDisabled = ( props = {} ) => { + // Further border properties to be added in future iterations. + // e.g. const configs = [ + // useIsBorderRadiusDisabled( props ), + // useIsBorderWidthDisabled( props ), + // ]; + const configs = [ useIsBorderRadiusDisabled( props ) ]; + return configs.filter( Boolean ).length === configs.length; +}; diff --git a/packages/block-editor/src/hooks/color-panel.js b/packages/block-editor/src/hooks/color-panel.js index 742c3b57e73636..9b5f49cce723c0 100644 --- a/packages/block-editor/src/hooks/color-panel.js +++ b/packages/block-editor/src/hooks/color-panel.js @@ -29,7 +29,7 @@ export default function ColorPanel( { return; } - const colorsDetectionElement = getBlockDOMNode( clientId ); + const colorsDetectionElement = getBlockDOMNode( clientId, document ); if ( ! colorsDetectionElement ) { return; } diff --git a/packages/block-editor/src/hooks/color.js b/packages/block-editor/src/hooks/color.js index 2f083d9af2f942..5490e045fbc056 100644 --- a/packages/block-editor/src/hooks/color.js +++ b/packages/block-editor/src/hooks/color.js @@ -34,9 +34,6 @@ export const COLOR_SUPPORT_KEY = 'color'; const EMPTY_ARRAY = []; const hasColorSupport = ( blockType ) => { - if ( Platform.OS !== 'web' ) { - return false; - } const colorSupport = getBlockSupport( blockType, COLOR_SUPPORT_KEY ); return ( colorSupport && @@ -68,20 +65,12 @@ const hasGradientSupport = ( blockType ) => { }; const hasBackgroundColorSupport = ( blockType ) => { - if ( Platform.OS !== 'web' ) { - return false; - } - const colorSupport = getBlockSupport( blockType, COLOR_SUPPORT_KEY ); return colorSupport && colorSupport.background !== false; }; const hasTextColorSupport = ( blockType ) => { - if ( Platform.OS !== 'web' ) { - return false; - } - const colorSupport = getBlockSupport( blockType, COLOR_SUPPORT_KEY ); return colorSupport && colorSupport.text !== false; @@ -226,7 +215,7 @@ export function ColorEdit( props ) { localAttributes.current = attributes; }, [ attributes ] ); - if ( ! hasColorSupport( blockName ) ) { + if ( ! hasColorSupport( blockName ) || Platform.OS !== 'web' ) { return null; } diff --git a/packages/block-editor/src/hooks/font-appearance.js b/packages/block-editor/src/hooks/font-appearance.js new file mode 100644 index 00000000000000..52661ce0364118 --- /dev/null +++ b/packages/block-editor/src/hooks/font-appearance.js @@ -0,0 +1,110 @@ +/** + * WordPress dependencies + */ +import { hasBlockSupport } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import FontAppearanceControl from '../components/font-appearance-control'; +import useEditorFeature from '../components/use-editor-feature'; +import { cleanEmptyObject } from './utils'; + +/** + * Key within block settings' support array indicating support for font style. + */ +export const FONT_STYLE_SUPPORT_KEY = '__experimentalFontStyle'; + +/** + * Key within block settings' support array indicating support for font weight. + */ +export const FONT_WEIGHT_SUPPORT_KEY = '__experimentalFontWeight'; + +/** + * Inspector control panel containing the font appearance options. + * + * @param {Object} props Block properties. + * @return {WPElement} Font appearance edit element. + */ +export function FontAppearanceEdit( props ) { + const { + attributes: { style }, + setAttributes, + } = props; + + const hasFontStyles = ! useIsFontStyleDisabled( props ); + const hasFontWeights = ! useIsFontWeightDisabled( props ); + + if ( ! hasFontStyles && ! hasFontWeights ) { + return null; + } + + const onChange = ( newStyles ) => { + setAttributes( { + style: cleanEmptyObject( { + ...style, + typography: { + ...style?.typography, + fontStyle: newStyles.fontStyle, + fontWeight: newStyles.fontWeight, + }, + } ), + } ); + }; + + const fontStyle = style?.typography?.fontStyle; + + const fontWeight = style?.typography?.fontWeight; + + return ( + + ); +} + +/** + * Checks if font style support has been disabled either by not opting in for + * support or by failing to provide preset styles. + * + * @param {Object} props Block properties. + * @param {string} props.name Name for the block type. + * @return {boolean} Whether font style support has been disabled. + */ +export function useIsFontStyleDisabled( { name: blockName } = {} ) { + const styleSupport = hasBlockSupport( blockName, FONT_STYLE_SUPPORT_KEY ); + const hasFontStyles = useEditorFeature( 'typography.customFontStyle' ); + + return ! styleSupport || ! hasFontStyles; +} + +/** + * Checks if font weight support has been disabled either by not opting in for + * support or by failing to provide preset weights. + * + * @param {Object} props Block properties. + * @param {string} props.name Name for the block type. + * @return {boolean} Whether font weight support has been disabled. + */ +export function useIsFontWeightDisabled( { name: blockName } = {} ) { + const weightSupport = hasBlockSupport( blockName, FONT_WEIGHT_SUPPORT_KEY ); + const hasFontWeights = useEditorFeature( 'typography.customFontWeight' ); + + return ! weightSupport || ! hasFontWeights; +} + +/** + * Checks if font appearance support has been disabled. + * + * @param {Object} props Block properties. + * @return {boolean} Whether font appearance support has been disabled. + */ +export function useIsFontAppearanceDisabled( props ) { + const stylesDisabled = useIsFontStyleDisabled( props ); + const weightsDisabled = useIsFontWeightDisabled( props ); + + return stylesDisabled && weightsDisabled; +} diff --git a/packages/block-editor/src/hooks/font-family.js b/packages/block-editor/src/hooks/font-family.js new file mode 100644 index 00000000000000..859606e9cffb1c --- /dev/null +++ b/packages/block-editor/src/hooks/font-family.js @@ -0,0 +1,91 @@ +/** + * External dependencies + */ +import { find } from 'lodash'; + +/** + * WordPress dependencies + */ +import { hasBlockSupport } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { cleanEmptyObject } from './utils'; +import useEditorFeature from '../components/use-editor-feature'; +import FontFamilyControl from '../components/font-family'; + +export const FONT_FAMILY_SUPPORT_KEY = '__experimentalFontFamily'; + +const getFontFamilyFromAttributeValue = ( fontFamilies, value ) => { + const attributeParsed = /var:preset\|font-family\|(.+)/.exec( value ); + if ( attributeParsed && attributeParsed[ 1 ] ) { + const fontFamilyObject = find( fontFamilies, ( { slug } ) => { + return slug === attributeParsed[ 1 ]; + } ); + if ( fontFamilyObject ) { + return fontFamilyObject.fontFamily; + } + } + return value; +}; + +export function FontFamilyEdit( { + name, + setAttributes, + attributes: { style = {} }, +} ) { + const fontFamilies = useEditorFeature( 'typography.fontFamilies' ); + const isDisable = useIsFontFamilyDisabled( { name } ); + + if ( isDisable ) { + return null; + } + + const value = getFontFamilyFromAttributeValue( + fontFamilies, + style.typography?.fontFamily + ); + + function onChange( newValue ) { + const predefinedFontFamily = find( + fontFamilies, + ( { fontFamily } ) => fontFamily === newValue + ); + setAttributes( { + style: cleanEmptyObject( { + ...style, + typography: { + ...( style.typography || {} ), + fontFamily: predefinedFontFamily + ? `var:preset|font-family|${ predefinedFontFamily.slug }` + : newValue || undefined, + }, + } ), + } ); + } + + return ( + + ); +} + +/** + * Custom hook that checks if font-family functionality is disabled. + * + * @param {string} name The name of the block. + * @return {boolean} Whether setting is disabled. + */ +export function useIsFontFamilyDisabled( { name } ) { + const fontFamilies = useEditorFeature( 'typography.fontFamilies' ); + return ( + ! fontFamilies || + fontFamilies.length === 0 || + ! hasBlockSupport( name, FONT_FAMILY_SUPPORT_KEY ) + ); +} diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index 5d9b2f5393a8dd..6aa2b76fc1d1f5 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -1,12 +1,10 @@ /** * Internal dependencies */ -import { AlignmentHookSettingsProvider } from './align'; +import './align'; import './anchor'; import './custom-class-name'; import './generated-class-name'; import './style'; import './color'; import './font-size'; - -export { AlignmentHookSettingsProvider }; diff --git a/packages/block-editor/src/hooks/index.native.js b/packages/block-editor/src/hooks/index.native.js index 5d9b2f5393a8dd..6aa2b76fc1d1f5 100644 --- a/packages/block-editor/src/hooks/index.native.js +++ b/packages/block-editor/src/hooks/index.native.js @@ -1,12 +1,10 @@ /** * Internal dependencies */ -import { AlignmentHookSettingsProvider } from './align'; +import './align'; import './anchor'; import './custom-class-name'; import './generated-class-name'; import './style'; import './color'; import './font-size'; - -export { AlignmentHookSettingsProvider }; diff --git a/packages/block-editor/src/hooks/padding.js b/packages/block-editor/src/hooks/padding.js index a83d6aff730f00..4f2d5ea51754c2 100644 --- a/packages/block-editor/src/hooks/padding.js +++ b/packages/block-editor/src/hooks/padding.js @@ -10,7 +10,7 @@ import { __experimentalBoxControl as BoxControl } from '@wordpress/components'; * Internal dependencies */ import { cleanEmptyObject } from './utils'; -import { useCustomUnits } from '../components/unit-control'; +import useEditorFeature from '../components/use-editor-feature'; export const SPACING_SUPPORT_KEY = 'spacing'; @@ -33,7 +33,11 @@ export function PaddingEdit( props ) { setAttributes, } = props; - const units = useCustomUnits(); + const customUnits = useEditorFeature( 'spacing.units' ); + const units = customUnits?.map( ( unit ) => ( { + value: unit, + label: unit, + } ) ); if ( ! hasPaddingSupport( blockName ) ) { return null; diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js index adec7bb0d02df0..125a850836f103 100644 --- a/packages/block-editor/src/hooks/style.js +++ b/packages/block-editor/src/hooks/style.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { has, get, startsWith } from 'lodash'; +import { has, get, startsWith, mapValues } from 'lodash'; /** * WordPress dependencies @@ -16,6 +16,7 @@ import { createHigherOrderComponent } from '@wordpress/compose'; /** * Internal dependencies */ +import { BORDER_SUPPORT_KEY, BorderPanel } from './border'; import { COLOR_SUPPORT_KEY, ColorEdit } from './color'; import { TypographyPanel, TYPOGRAPHY_SUPPORT_KEYS } from './typography'; import { SPACING_SUPPORT_KEY, PaddingEdit } from './padding'; @@ -23,6 +24,7 @@ import SpacingPanelControl from '../components/spacing-panel-control'; const styleSupportKeys = [ ...TYPOGRAPHY_SUPPORT_KEYS, + BORDER_SUPPORT_KEY, COLOR_SUPPORT_KEY, SPACING_SUPPORT_KEY, ]; @@ -52,7 +54,8 @@ function compileStyleValue( uncompiledValue ) { */ export function getInlineStyles( styles = {} ) { const output = {}; - Object.entries( STYLE_PROPERTY ).forEach( + const styleProperties = mapValues( STYLE_PROPERTY, ( prop ) => prop.value ); + Object.entries( styleProperties ).forEach( ( [ styleKey, ...otherObjectKeys ] ) => { const [ objectKeys ] = otherObjectKeys; @@ -155,6 +158,7 @@ export const withBlockControls = createHigherOrderComponent( return [ , + , , , hasSpacingSupport && ( diff --git a/packages/block-editor/src/hooks/test/align.js b/packages/block-editor/src/hooks/test/align.js index 3feb9473433b34..903ba635c94bdf 100644 --- a/packages/block-editor/src/hooks/test/align.js +++ b/packages/block-editor/src/hooks/test/align.js @@ -74,6 +74,18 @@ describe( 'align', () => { ] ); } ); + it( 'should return all aligns sorted when provided in the random order', () => { + expect( + getValidAlignments( [ + 'full', + 'right', + 'center', + 'wide', + 'left', + ] ) + ).toEqual( [ 'left', 'center', 'right', 'wide', 'full' ] ); + } ); + it( 'should return all aligns if block defines align support as true', () => { expect( getValidAlignments( true ) ).toEqual( [ 'left', @@ -83,7 +95,6 @@ describe( 'align', () => { 'full', ] ); } ); - it( 'should return all aligns except wide if wide align explicitly false on the block', () => { expect( getValidAlignments( true, false, true ) ).toEqual( [ 'left', diff --git a/packages/block-editor/src/hooks/test/style.js b/packages/block-editor/src/hooks/test/style.js index 533694be7282ed..62c5a97b6e211e 100644 --- a/packages/block-editor/src/hooks/test/style.js +++ b/packages/block-editor/src/hooks/test/style.js @@ -17,9 +17,11 @@ describe( 'getInlineStyles', () => { getInlineStyles( { color: { text: 'red', background: 'black' }, typography: { lineHeight: 1.5, fontSize: 10 }, + border: { radius: 10 }, } ) ).toEqual( { backgroundColor: 'black', + borderRadius: 10, color: 'red', lineHeight: 1.5, fontSize: 10, diff --git a/packages/block-editor/src/hooks/text-decoration.js b/packages/block-editor/src/hooks/text-decoration.js new file mode 100644 index 00000000000000..d2433277e8d4b8 --- /dev/null +++ b/packages/block-editor/src/hooks/text-decoration.js @@ -0,0 +1,72 @@ +/** + * WordPress dependencies + */ +import { hasBlockSupport } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import TextDecorationControl from '../components/text-decoration-control'; +import useEditorFeature from '../components/use-editor-feature'; +import { cleanEmptyObject } from './utils'; + +/** + * Key within block settings' supports array indicating support for text + * decorations e.g. settings found in `block.json`. + */ +export const TEXT_DECORATION_SUPPORT_KEY = '__experimentalTextDecoration'; + +/** + * Inspector control panel containing the text decoration options. + * + * @param {Object} props Block properties. + * @return {WPElement} Text decoration edit element. + */ +export function TextDecorationEdit( props ) { + const { + attributes: { style }, + setAttributes, + } = props; + const isDisabled = useIsTextDecorationDisabled( props ); + + if ( isDisabled ) { + return null; + } + + function onChange( newDecoration ) { + setAttributes( { + style: cleanEmptyObject( { + ...style, + typography: { + ...style?.typography, + textDecoration: newDecoration, + }, + } ), + } ); + } + + return ( + + ); +} + +/** + * Checks if text-decoration settings have been disabled. + * + * @param {string} name Name of the block. + * @return {boolean} Whether or not the setting is disabled. + */ +export function useIsTextDecorationDisabled( { name: blockName } = {} ) { + const notSupported = ! hasBlockSupport( + blockName, + TEXT_DECORATION_SUPPORT_KEY + ); + const hasTextDecoration = useEditorFeature( + 'typography.customTextDecorations' + ); + + return notSupported || ! hasTextDecoration; +} diff --git a/packages/block-editor/src/hooks/text-transform.js b/packages/block-editor/src/hooks/text-transform.js new file mode 100644 index 00000000000000..fd317861f0e717 --- /dev/null +++ b/packages/block-editor/src/hooks/text-transform.js @@ -0,0 +1,71 @@ +/** + * WordPress dependencies + */ +import { hasBlockSupport } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import TextTransformControl from '../components/text-transform-control'; +import useEditorFeature from '../components/use-editor-feature'; +import { cleanEmptyObject } from './utils'; + +/** + * Key within block settings' supports array indicating support for text + * transforms e.g. settings found in `block.json`. + */ +export const TEXT_TRANSFORM_SUPPORT_KEY = '__experimentalTextTransform'; + +/** + * Inspector control panel containing the text transform options. + * + * @param {Object} props Block properties. + * @return {WPElement} Text transform edit element. + */ +export function TextTransformEdit( props ) { + const { + attributes: { style }, + setAttributes, + } = props; + const isDisabled = useIsTextTransformDisabled( props ); + + if ( isDisabled ) { + return null; + } + + function onChange( newTransform ) { + setAttributes( { + style: cleanEmptyObject( { + ...style, + typography: { + ...style?.typography, + textTransform: newTransform, + }, + } ), + } ); + } + + return ( + + ); +} + +/** + * Checks if text-transform settings have been disabled. + * + * @param {string} name Name of the block. + * @return {boolean} Whether or not the setting is disabled. + */ +export function useIsTextTransformDisabled( { name: blockName } = {} ) { + const notSupported = ! hasBlockSupport( + blockName, + TEXT_TRANSFORM_SUPPORT_KEY + ); + const hasTextTransforms = useEditorFeature( + 'typography.customTextTransforms' + ); + return notSupported || ! hasTextTransforms; +} diff --git a/packages/block-editor/src/hooks/typography.js b/packages/block-editor/src/hooks/typography.js index 49a281cb219eaf..dfc27748acb00f 100644 --- a/packages/block-editor/src/hooks/typography.js +++ b/packages/block-editor/src/hooks/typography.js @@ -10,21 +10,46 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import InspectorControls from '../components/inspector-controls'; +import TextDecorationAndTransformEdit from '../components/text-decoration-and-transform'; import { LINE_HEIGHT_SUPPORT_KEY, LineHeightEdit, useIsLineHeightDisabled, } from './line-height'; +import { + FONT_STYLE_SUPPORT_KEY, + FONT_WEIGHT_SUPPORT_KEY, + FontAppearanceEdit, + useIsFontAppearanceDisabled, +} from './font-appearance'; +import { + FONT_FAMILY_SUPPORT_KEY, + FontFamilyEdit, + useIsFontFamilyDisabled, +} from './font-family'; import { FONT_SIZE_SUPPORT_KEY, FontSizeEdit, useIsFontSizeDisabled, } from './font-size'; +import { + TEXT_DECORATION_SUPPORT_KEY, + useIsTextDecorationDisabled, +} from './text-decoration'; +import { + TEXT_TRANSFORM_SUPPORT_KEY, + useIsTextTransformDisabled, +} from './text-transform'; export const TYPOGRAPHY_SUPPORT_KEYS = [ LINE_HEIGHT_SUPPORT_KEY, FONT_SIZE_SUPPORT_KEY, + FONT_STYLE_SUPPORT_KEY, + FONT_WEIGHT_SUPPORT_KEY, + FONT_FAMILY_SUPPORT_KEY, + TEXT_DECORATION_SUPPORT_KEY, + TEXT_TRANSFORM_SUPPORT_KEY, ]; export function TypographyPanel( props ) { @@ -36,8 +61,11 @@ export function TypographyPanel( props ) { return ( + + + ); @@ -54,8 +82,12 @@ const hasTypographySupport = ( blockName ) => { function useIsTypographyDisabled( props = {} ) { const configs = [ + useIsFontAppearanceDisabled( props ), useIsFontSizeDisabled( props ), useIsLineHeightDisabled( props ), + useIsFontFamilyDisabled( props ), + useIsTextDecorationDisabled( props ), + useIsTextTransformDisabled( props ), ]; return configs.filter( Boolean ).length === configs.length; diff --git a/packages/block-editor/src/index.js b/packages/block-editor/src/index.js index add6f5a5f9910e..e8a1ebcd480c2b 100644 --- a/packages/block-editor/src/index.js +++ b/packages/block-editor/src/index.js @@ -1,18 +1,13 @@ /** * WordPress dependencies */ -import '@wordpress/blocks'; import '@wordpress/rich-text'; -import '@wordpress/viewport'; -import '@wordpress/keyboard-shortcuts'; -import '@wordpress/notices'; /** * Internal dependencies */ -import { AlignmentHookSettingsProvider as __experimentalAlignmentHookSettingsProvider } from './hooks'; -export { __experimentalAlignmentHookSettingsProvider }; +import './hooks'; export * from './components'; export * from './utils'; -export { storeConfig } from './store'; +export { storeConfig, store } from './store'; export { SETTINGS_DEFAULTS } from './store/defaults'; diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 7aaca6c9fd1ec8..cf69468a598a29 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { castArray, first, last, some } from 'lodash'; +import { castArray, findKey, first, last, some } from 'lodash'; /** * WordPress dependencies @@ -9,12 +9,22 @@ import { castArray, first, last, some } from 'lodash'; import { cloneBlock, createBlock, + doBlocksMatchTemplate, + getBlockType, getDefaultBlockName, hasBlockSupport, + switchToBlockType, + synchronizeBlocksWithTemplate, } from '@wordpress/blocks'; import { speak } from '@wordpress/a11y'; -import { __ } from '@wordpress/i18n'; +import { __, _n, sprintf } from '@wordpress/i18n'; import { controls } from '@wordpress/data'; +import { create, insert, remove, toHTMLString } from '@wordpress/rich-text'; + +/** + * Internal dependencies + */ +import { __unstableMarkAutomaticChangeFinalControl } from '../store/controls'; /** * Generator which will yield a default block insert action if there @@ -38,14 +48,50 @@ function* ensureDefaultBlock() { * content reflected as an edit in state. * * @param {Array} blocks Array of blocks. - * - * @return {Object} Action object. */ -export function resetBlocks( blocks ) { - return { +export function* resetBlocks( blocks ) { + yield { type: 'RESET_BLOCKS', blocks, }; + return yield* validateBlocksToTemplate( blocks ); +} + +/** + * Block validity is a function of blocks state (at the point of a + * reset) and the template setting. As a compromise to its placement + * across distinct parts of state, it is implemented here as a side- + * effect of the block reset action. + * + * @param {Array} blocks Array of blocks. + */ +export function* validateBlocksToTemplate( blocks ) { + const template = yield controls.select( + 'core/block-editor', + 'getTemplate' + ); + const templateLock = yield controls.select( + 'core/block-editor', + 'getTemplateLock' + ); + + // Unlocked templates are considered always valid because they act + // as default values only. + const isBlocksValidToTemplate = + ! template || + templateLock !== 'all' || + doBlocksMatchTemplate( blocks, template ); + + // Update if validity has changed. + const isValidTemplate = yield controls.select( + 'core/block-editor', + 'isValidTemplate' + ); + + if ( isBlocksValidToTemplate !== isValidTemplate ) { + yield setTemplateValidity( isBlocksValidToTemplate ); + return isBlocksValidToTemplate; + } } /** @@ -211,15 +257,27 @@ export function stopMultiSelect() { * * @param {string} start First block of the multi selection. * @param {string} end Last block of the multiselection. - * - * @return {Object} Action object. */ -export function multiSelect( start, end ) { - return { +export function* multiSelect( start, end ) { + yield { type: 'MULTI_SELECT', start, end, }; + + const blockCount = yield controls.select( + 'core/block-editor', + 'getSelectedBlockCount' + ); + + speak( + sprintf( + /* translators: %s: number of selected blocks */ + _n( '%s block selected.', '%s blocks selected.', blockCount ), + blockCount + ), + 'assertive' + ); } /** @@ -592,10 +650,18 @@ export function setTemplateValidity( isValid ) { * * @return {Object} Action object. */ -export function synchronizeTemplate() { - return { +export function* synchronizeTemplate() { + yield { type: 'SYNCHRONIZE_TEMPLATE', }; + const blocks = yield controls.select( 'core/block-editor', 'getBlocks' ); + const template = yield controls.select( + 'core/block-editor', + 'getTemplate' + ); + const updatedBlockList = synchronizeBlocksWithTemplate( blocks, template ); + + return yield resetBlocks( updatedBlockList ); } /** @@ -603,14 +669,165 @@ export function synchronizeTemplate() { * * @param {string} firstBlockClientId Client ID of the first block to merge. * @param {string} secondBlockClientId Client ID of the second block to merge. - * - * @return {Object} Action object. */ -export function mergeBlocks( firstBlockClientId, secondBlockClientId ) { - return { +export function* mergeBlocks( firstBlockClientId, secondBlockClientId ) { + const blocks = [ firstBlockClientId, secondBlockClientId ]; + yield { type: 'MERGE_BLOCKS', - blocks: [ firstBlockClientId, secondBlockClientId ], + blocks, }; + + const [ clientIdA, clientIdB ] = blocks; + const blockA = yield controls.select( + 'core/block-editor', + 'getBlock', + clientIdA + ); + const blockAType = getBlockType( blockA.name ); + + // Only focus the previous block if it's not mergeable + if ( ! blockAType.merge ) { + yield selectBlock( blockA.clientId ); + return; + } + + const blockB = yield controls.select( + 'core/block-editor', + 'getBlock', + clientIdB + ); + const blockBType = getBlockType( blockB.name ); + const { clientId, attributeKey, offset } = yield controls.select( + 'core/block-editor', + 'getSelectionStart' + ); + const selectedBlockType = clientId === clientIdA ? blockAType : blockBType; + const attributeDefinition = selectedBlockType.attributes[ attributeKey ]; + const canRestoreTextSelection = + ( clientId === clientIdA || clientId === clientIdB ) && + attributeKey !== undefined && + offset !== undefined && + // We cannot restore text selection if the RichText identifier + // is not a defined block attribute key. This can be the case if the + // fallback intance ID is used to store selection (and no RichText + // identifier is set), or when the identifier is wrong. + !! attributeDefinition; + + if ( ! attributeDefinition ) { + if ( typeof attributeKey === 'number' ) { + window.console.error( + `RichText needs an identifier prop that is the block attribute key of the attribute it controls. Its type is expected to be a string, but was ${ typeof attributeKey }` + ); + } else { + window.console.error( + 'The RichText identifier prop does not match any attributes defined by the block.' + ); + } + } + + // A robust way to retain selection position through various transforms + // is to insert a special character at the position and then recover it. + const START_OF_SELECTED_AREA = '\u0086'; + + // Clone the blocks so we don't insert the character in a "live" block. + const cloneA = cloneBlock( blockA ); + const cloneB = cloneBlock( blockB ); + + if ( canRestoreTextSelection ) { + const selectedBlock = clientId === clientIdA ? cloneA : cloneB; + const html = selectedBlock.attributes[ attributeKey ]; + const { + multiline: multilineTag, + __unstableMultilineWrapperTags: multilineWrapperTags, + __unstablePreserveWhiteSpace: preserveWhiteSpace, + } = attributeDefinition; + const value = insert( + create( { + html, + multilineTag, + multilineWrapperTags, + preserveWhiteSpace, + } ), + START_OF_SELECTED_AREA, + offset, + offset + ); + + selectedBlock.attributes[ attributeKey ] = toHTMLString( { + value, + multilineTag, + preserveWhiteSpace, + } ); + } + + // We can only merge blocks with similar types + // thus, we transform the block to merge first + const blocksWithTheSameType = + blockA.name === blockB.name + ? [ cloneB ] + : switchToBlockType( cloneB, blockA.name ); + + // If the block types can not match, do nothing + if ( ! blocksWithTheSameType || ! blocksWithTheSameType.length ) { + return; + } + + // Calling the merge to update the attributes and remove the block to be merged + const updatedAttributes = blockAType.merge( + cloneA.attributes, + blocksWithTheSameType[ 0 ].attributes + ); + + if ( canRestoreTextSelection ) { + const newAttributeKey = findKey( + updatedAttributes, + ( v ) => + typeof v === 'string' && + v.indexOf( START_OF_SELECTED_AREA ) !== -1 + ); + const convertedHtml = updatedAttributes[ newAttributeKey ]; + const { + multiline: multilineTag, + __unstableMultilineWrapperTags: multilineWrapperTags, + __unstablePreserveWhiteSpace: preserveWhiteSpace, + } = blockAType.attributes[ newAttributeKey ]; + const convertedValue = create( { + html: convertedHtml, + multilineTag, + multilineWrapperTags, + preserveWhiteSpace, + } ); + const newOffset = convertedValue.text.indexOf( START_OF_SELECTED_AREA ); + const newValue = remove( convertedValue, newOffset, newOffset + 1 ); + const newHtml = toHTMLString( { + value: newValue, + multilineTag, + preserveWhiteSpace, + } ); + + updatedAttributes[ newAttributeKey ] = newHtml; + + yield selectionChange( + blockA.clientId, + newAttributeKey, + newOffset, + newOffset + ); + } + + yield* replaceBlocks( + [ blockA.clientId, blockB.clientId ], + [ + { + ...blockA, + attributes: { + ...blockA.attributes, + ...updatedAttributes, + }, + }, + ...blocksWithTheSameType.slice( 1 ), + ] + ); } /** @@ -683,14 +900,14 @@ export function removeBlock( clientId, selectPrevious ) { * * @param {string} rootClientId Client ID of the block whose InnerBlocks will re replaced. * @param {Object[]} blocks Block objects to insert as new InnerBlocks - * @param {?boolean} updateSelection If true block selection will be updated. If false, block selection will not change. Defaults to true. + * @param {?boolean} updateSelection If true block selection will be updated. If false, block selection will not change. Defaults to false. * * @return {Object} Action object. */ export function replaceInnerBlocks( rootClientId, blocks, - updateSelection = true + updateSelection = false ) { return { type: 'REPLACE_INNER_BLOCKS', @@ -907,11 +1124,16 @@ export function __unstableMarkNextChangeAsNotPersistent() { * after the change was made, and any actions that are a consequence of it, so * it is recommended to be called at the next idle period to ensure all * selection changes have been recorded. - * - * @return {Object} Action object. */ -export function __unstableMarkAutomaticChange() { - return { type: 'MARK_AUTOMATIC_CHANGE' }; +export function* __unstableMarkAutomaticChange() { + yield { type: 'MARK_AUTOMATIC_CHANGE' }; + yield __unstableMarkAutomaticChangeFinalControl(); +} + +export function __unstableMarkAutomaticChangeFinal() { + return { + type: 'MARK_AUTOMATIC_CHANGE_FINAL', + }; } /** diff --git a/packages/block-editor/src/store/controls.js b/packages/block-editor/src/store/controls.js index 83b4f453425462..84d97f38654bb0 100644 --- a/packages/block-editor/src/store/controls.js +++ b/packages/block-editor/src/store/controls.js @@ -1,9 +1,34 @@ +/** + * WordPress dependencies + */ +import { createRegistryControl } from '@wordpress/data'; + +export const __unstableMarkAutomaticChangeFinalControl = function () { + return { + type: 'MARK_AUTOMATIC_CHANGE_FINAL_CONTROL', + }; +}; + const controls = { SLEEP( { duration } ) { return new Promise( ( resolve ) => { setTimeout( resolve, duration ); } ); }, + + MARK_AUTOMATIC_CHANGE_FINAL_CONTROL: createRegistryControl( + ( registry ) => () => { + const { + requestIdleCallback = ( callback ) => + setTimeout( callback, 100 ), + } = window; + requestIdleCallback( () => + registry + .dispatch( 'core/block-editor' ) + .__unstableMarkAutomaticChangeFinal() + ); + } + ), }; export default controls; diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js index e8c61c99821686..8bbcbff537dbb8 100644 --- a/packages/block-editor/src/store/defaults.js +++ b/packages/block-editor/src/store/defaults.js @@ -27,8 +27,6 @@ export const PREFERENCES_DEFAULTS = { * @property {boolean} codeEditingEnabled Whether or not the user can switch to the code editor * @property {boolean} __experimentalCanUserUseUnfilteredHTML Whether the user should be able to use unfiltered HTML or the HTML should be filtered e.g., to remove elements considered insecure like iframes. * @property {boolean} __experimentalBlockDirectory Whether the user has enabled the Block Directory - * @property {boolean} __experimentalEnableFullSiteEditing Whether the user has enabled Full Site Editing - * @property {boolean} __experimentalEnableFullSiteEditingDemo Whether the user has enabled Full Site Editing Demo Templates * @property {Array} __experimentalBlockPatterns Array of objects representing the block patterns * @property {Array} __experimentalBlockPatternCategories Array of objects representing the block pattern categories */ @@ -150,8 +148,6 @@ export const SETTINGS_DEFAULTS = { availableLegacyWidgets: {}, __experimentalCanUserUseUnfilteredHTML: false, __experimentalBlockDirectory: false, - __experimentalEnableFullSiteEditing: false, - __experimentalEnableFullSiteEditingDemo: false, __mobileEnablePageTemplates: false, __experimentalBlockPatterns: [], __experimentalBlockPatternCategories: [], diff --git a/packages/block-editor/src/store/effects.js b/packages/block-editor/src/store/effects.js deleted file mode 100644 index 5142020ffffa1e..00000000000000 --- a/packages/block-editor/src/store/effects.js +++ /dev/null @@ -1,256 +0,0 @@ -/** - * External dependencies - */ -import { findKey } from 'lodash'; - -/** - * WordPress dependencies - */ -import { speak } from '@wordpress/a11y'; -import { - getBlockType, - doBlocksMatchTemplate, - switchToBlockType, - synchronizeBlocksWithTemplate, - cloneBlock, -} from '@wordpress/blocks'; -import { _n, sprintf } from '@wordpress/i18n'; -import { create, toHTMLString, insert, remove } from '@wordpress/rich-text'; - -/** - * Internal dependencies - */ -import { - replaceBlocks, - selectBlock, - setTemplateValidity, - resetBlocks, - selectionChange, -} from './actions'; -import { - getBlock, - getBlocks, - getSelectedBlockCount, - getTemplateLock, - getTemplate, - isValidTemplate, - getSelectionStart, -} from './selectors'; - -/** - * Block validity is a function of blocks state (at the point of a - * reset) and the template setting. As a compromise to its placement - * across distinct parts of state, it is implemented here as a side- - * effect of the block reset action. - * - * @param {Object} action RESET_BLOCKS action. - * @param {Object} store Store instance. - * - * @return {?Object} New validity set action if validity has changed. - */ -export function validateBlocksToTemplate( action, store ) { - const state = store.getState(); - const template = getTemplate( state ); - const templateLock = getTemplateLock( state ); - - // Unlocked templates are considered always valid because they act - // as default values only. - const isBlocksValidToTemplate = - ! template || - templateLock !== 'all' || - doBlocksMatchTemplate( action.blocks, template ); - - // Update if validity has changed. - if ( isBlocksValidToTemplate !== isValidTemplate( state ) ) { - return setTemplateValidity( isBlocksValidToTemplate ); - } -} - -export default { - MERGE_BLOCKS( action, store ) { - const { dispatch } = store; - const state = store.getState(); - const [ clientIdA, clientIdB ] = action.blocks; - const blockA = getBlock( state, clientIdA ); - const blockAType = getBlockType( blockA.name ); - - // Only focus the previous block if it's not mergeable - if ( ! blockAType.merge ) { - dispatch( selectBlock( blockA.clientId ) ); - return; - } - - const blockB = getBlock( state, clientIdB ); - const blockBType = getBlockType( blockB.name ); - const { clientId, attributeKey, offset } = getSelectionStart( state ); - const selectedBlockType = - clientId === clientIdA ? blockAType : blockBType; - const attributeDefinition = - selectedBlockType.attributes[ attributeKey ]; - const canRestoreTextSelection = - ( clientId === clientIdA || clientId === clientIdB ) && - attributeKey !== undefined && - offset !== undefined && - // We cannot restore text selection if the RichText identifier - // is not a defined block attribute key. This can be the case if the - // fallback intance ID is used to store selection (and no RichText - // identifier is set), or when the identifier is wrong. - !! attributeDefinition; - - if ( ! attributeDefinition ) { - if ( typeof attributeKey === 'number' ) { - window.console.error( - `RichText needs an identifier prop that is the block attribute key of the attribute it controls. Its type is expected to be a string, but was ${ typeof attributeKey }` - ); - } else { - window.console.error( - 'The RichText identifier prop does not match any attributes defined by the block.' - ); - } - } - - // A robust way to retain selection position through various transforms - // is to insert a special character at the position and then recover it. - const START_OF_SELECTED_AREA = '\u0086'; - - // Clone the blocks so we don't insert the character in a "live" block. - const cloneA = cloneBlock( blockA ); - const cloneB = cloneBlock( blockB ); - - if ( canRestoreTextSelection ) { - const selectedBlock = clientId === clientIdA ? cloneA : cloneB; - const html = selectedBlock.attributes[ attributeKey ]; - const { - multiline: multilineTag, - __unstableMultilineWrapperTags: multilineWrapperTags, - __unstablePreserveWhiteSpace: preserveWhiteSpace, - } = attributeDefinition; - const value = insert( - create( { - html, - multilineTag, - multilineWrapperTags, - preserveWhiteSpace, - } ), - START_OF_SELECTED_AREA, - offset, - offset - ); - - selectedBlock.attributes[ attributeKey ] = toHTMLString( { - value, - multilineTag, - preserveWhiteSpace, - } ); - } - - // We can only merge blocks with similar types - // thus, we transform the block to merge first - const blocksWithTheSameType = - blockA.name === blockB.name - ? [ cloneB ] - : switchToBlockType( cloneB, blockA.name ); - - // If the block types can not match, do nothing - if ( ! blocksWithTheSameType || ! blocksWithTheSameType.length ) { - return; - } - - // Calling the merge to update the attributes and remove the block to be merged - const updatedAttributes = blockAType.merge( - cloneA.attributes, - blocksWithTheSameType[ 0 ].attributes - ); - - if ( canRestoreTextSelection ) { - const newAttributeKey = findKey( - updatedAttributes, - ( v ) => - typeof v === 'string' && - v.indexOf( START_OF_SELECTED_AREA ) !== -1 - ); - const convertedHtml = updatedAttributes[ newAttributeKey ]; - const { - multiline: multilineTag, - __unstableMultilineWrapperTags: multilineWrapperTags, - __unstablePreserveWhiteSpace: preserveWhiteSpace, - } = blockAType.attributes[ newAttributeKey ]; - const convertedValue = create( { - html: convertedHtml, - multilineTag, - multilineWrapperTags, - preserveWhiteSpace, - } ); - const newOffset = convertedValue.text.indexOf( - START_OF_SELECTED_AREA - ); - const newValue = remove( convertedValue, newOffset, newOffset + 1 ); - const newHtml = toHTMLString( { - value: newValue, - multilineTag, - preserveWhiteSpace, - } ); - - updatedAttributes[ newAttributeKey ] = newHtml; - - dispatch( - selectionChange( - blockA.clientId, - newAttributeKey, - newOffset, - newOffset - ) - ); - } - - dispatch( - replaceBlocks( - [ blockA.clientId, blockB.clientId ], - [ - { - ...blockA, - attributes: { - ...blockA.attributes, - ...updatedAttributes, - }, - }, - ...blocksWithTheSameType.slice( 1 ), - ] - ) - ); - }, - RESET_BLOCKS: [ validateBlocksToTemplate ], - MULTI_SELECT: ( action, { getState } ) => { - const blockCount = getSelectedBlockCount( getState() ); - - speak( - sprintf( - /* translators: %s: number of selected blocks */ - _n( '%s block selected.', '%s blocks selected.', blockCount ), - blockCount - ), - 'assertive' - ); - }, - SYNCHRONIZE_TEMPLATE( action, { getState } ) { - const state = getState(); - const blocks = getBlocks( state ); - const template = getTemplate( state ); - const updatedBlockList = synchronizeBlocksWithTemplate( - blocks, - template - ); - - return resetBlocks( updatedBlockList ); - }, - MARK_AUTOMATIC_CHANGE( action, store ) { - const { - setTimeout, - requestIdleCallback = ( callback ) => setTimeout( callback, 100 ), - } = window; - - requestIdleCallback( () => { - store.dispatch( { type: 'MARK_AUTOMATIC_CHANGE_FINAL' } ); - } ); - }, -}; diff --git a/packages/block-editor/src/store/index.js b/packages/block-editor/src/store/index.js index bfc7766a508762..4ba66689166777 100644 --- a/packages/block-editor/src/store/index.js +++ b/packages/block-editor/src/store/index.js @@ -1,13 +1,12 @@ /** * WordPress dependencies */ -import { registerStore } from '@wordpress/data'; +import { createReduxStore, registerStore } from '@wordpress/data'; /** * Internal dependencies */ import reducer from './reducer'; -import applyMiddlewares from './middlewares'; import * as selectors from './selectors'; import * as actions from './actions'; import controls from './controls'; @@ -15,7 +14,7 @@ import controls from './controls'; /** * Module Constants */ -const MODULE_KEY = 'core/block-editor'; +const STORE_NAME = 'core/block-editor'; /** * Block editor data store configuration. @@ -31,10 +30,20 @@ export const storeConfig = { controls, }; -const store = registerStore( MODULE_KEY, { +/** + * Store definition for the block editor namespace. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore + * + * @type {Object} + */ +export const store = createReduxStore( STORE_NAME, { ...storeConfig, persist: [ 'preferences' ], } ); -applyMiddlewares( store ); -export default store; +// Ideally we'd use register instead of register stores. +registerStore( STORE_NAME, { + ...storeConfig, + persist: [ 'preferences' ], +} ); diff --git a/packages/block-editor/src/store/middlewares.js b/packages/block-editor/src/store/middlewares.js deleted file mode 100644 index 0f4c5aef8df703..00000000000000 --- a/packages/block-editor/src/store/middlewares.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * External dependencies - */ -import refx from 'refx'; -import multi from 'redux-multi'; -import { flowRight } from 'lodash'; - -/** - * Internal dependencies - */ -import effects from './effects'; - -/** - * Applies the custom middlewares used specifically in the editor module. - * - * @param {Object} store Store Object. - * - * @return {Object} Update Store Object. - */ -function applyMiddlewares( store ) { - const middlewares = [ refx( effects ), multi ]; - - let enhancedDispatch = () => { - throw new Error( - 'Dispatching while constructing your middleware is not allowed. ' + - 'Other middleware would not be applied to this dispatch.' - ); - }; - let chain = []; - - const middlewareAPI = { - getState: store.getState, - dispatch: ( ...args ) => enhancedDispatch( ...args ), - }; - chain = middlewares.map( ( middleware ) => middleware( middlewareAPI ) ); - enhancedDispatch = flowRight( ...chain )( store.dispatch ); - - store.dispatch = enhancedDispatch; - return store; -} - -export default applyMiddlewares; diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 20a57146bb42c4..366d47b7d77f2e 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -1585,6 +1585,10 @@ export function hasBlockMovingClientId( state = null, action ) { return action.hasBlockMovingClientId; } + if ( action.type === 'SET_NAVIGATION_MODE' ) { + return null; + } + return state; } @@ -1649,6 +1653,7 @@ export function automaticChangeStatus( state, action ) { return; // Undoing an automatic change should still be possible after mouse // move. + case 'START_TYPING': case 'STOP_TYPING': return state; } diff --git a/packages/block-editor/src/store/test/actions.js b/packages/block-editor/src/store/test/actions.js index 94379114f31bfd..d9598b9a7e6b86 100644 --- a/packages/block-editor/src/store/test/actions.js +++ b/packages/block-editor/src/store/test/actions.js @@ -41,10 +41,10 @@ import { describe( 'actions', () => { describe( 'resetBlocks', () => { - it( 'should return the RESET_BLOCKS actions', () => { + it( 'should yield the RESET_BLOCKS actions', () => { const blocks = []; - const result = resetBlocks( blocks ); - expect( result ).toEqual( { + const fulfillment = resetBlocks( blocks ); + expect( fulfillment.next().value ).toEqual( { type: 'RESET_BLOCKS', blocks, } ); @@ -119,7 +119,8 @@ describe( 'actions', () => { it( 'should return MULTI_SELECT action', () => { const start = 'start'; const end = 'end'; - expect( multiSelect( start, end ) ).toEqual( { + const fulfillment = multiSelect( start, end ); + expect( fulfillment.next().value ).toEqual( { type: 'MULTI_SELECT', start, end, @@ -740,9 +741,11 @@ describe( 'actions', () => { it( 'should return MERGE_BLOCKS action', () => { const firstBlockClientId = 'blockA'; const secondBlockClientId = 'blockB'; - expect( - mergeBlocks( firstBlockClientId, secondBlockClientId ) - ).toEqual( { + const fulfillment = mergeBlocks( + firstBlockClientId, + secondBlockClientId + ); + expect( fulfillment.next().value ).toEqual( { type: 'MERGE_BLOCKS', blocks: [ firstBlockClientId, secondBlockClientId ], } ); @@ -1121,17 +1124,17 @@ describe( 'actions', () => { blocks: [ block ], rootClientId: 'root', time: expect.any( Number ), - updateSelection: true, + updateSelection: false, } ); } ); - it( 'should return the REPLACE_INNER_BLOCKS action with updateSelection false', () => { - expect( replaceInnerBlocks( 'root', [ block ], false ) ).toEqual( { + it( 'should return the REPLACE_INNER_BLOCKS action with updateSelection true', () => { + expect( replaceInnerBlocks( 'root', [ block ], true ) ).toEqual( { type: 'REPLACE_INNER_BLOCKS', blocks: [ block ], rootClientId: 'root', time: expect.any( Number ), - updateSelection: false, + updateSelection: true, } ); } ); } ); diff --git a/packages/block-editor/src/store/test/effects.js b/packages/block-editor/src/store/test/effects.js index 1832e7fe2a8292..9a8ecc49801875 100644 --- a/packages/block-editor/src/store/test/effects.js +++ b/packages/block-editor/src/store/test/effects.js @@ -21,16 +21,13 @@ import { createRegistry } from '@wordpress/data'; import actions, { updateSettings, mergeBlocks, - replaceBlocks, resetBlocks, selectBlock, selectionChange, - setTemplateValidity, + validateBlocksToTemplate, } from '../actions'; -import effects, { validateBlocksToTemplate } from '../effects'; import * as selectors from '../selectors'; import reducer from '../reducer'; -import applyMiddlewares from '../middlewares'; import '../../'; describe( 'effects', () => { @@ -44,14 +41,10 @@ describe( 'effects', () => { }; describe( '.MERGE_BLOCKS', () => { - const handler = effects.MERGE_BLOCKS; - const defaultGetBlock = selectors.getBlock; - afterEach( () => { getBlockTypes().forEach( ( block ) => { unregisterBlockType( block.name ); } ); - selectors.getBlock = defaultGetBlock; } ); it( 'should only focus the blockA if the blockA has no merge function', () => { @@ -64,19 +57,21 @@ describe( 'effects', () => { clientId: 'ribs', name: 'core/test-block', } ); - selectors.getBlock = ( state, clientId ) => { - return blockA.clientId === clientId ? blockA : blockB; - }; - const dispatch = jest.fn(); - const getState = () => ( {} ); - handler( mergeBlocks( blockA.clientId, blockB.clientId ), { - dispatch, - getState, + const fulfillment = mergeBlocks( blockA.clientId, blockB.clientId ); + expect( fulfillment.next() ).toEqual( { + done: false, + value: { + type: 'MERGE_BLOCKS', + blocks: [ blockA.clientId, blockB.clientId ], + }, } ); - - expect( dispatch ).toHaveBeenCalledTimes( 1 ); - expect( dispatch ).toHaveBeenCalledWith( selectBlock( 'chicken' ) ); + fulfillment.next(); + expect( fulfillment.next( blockA ) ).toEqual( { + done: false, + value: selectBlock( 'chicken' ), + } ); + expect( fulfillment.next( blockA ).done ).toEqual( true ); } ); it( 'should merge the blocks if blocks of the same type', () => { @@ -108,24 +103,23 @@ describe( 'effects', () => { attributes: { content: 'ribs' }, innerBlocks: [], } ); - selectors.getBlock = ( state, clientId ) => { - return blockA.clientId === clientId ? blockA : blockB; - }; - const dispatch = jest.fn(); - const getState = () => ( { - selectionStart: { - clientId: blockB.clientId, - attributeKey: 'content', - offset: 0, - }, - } ); - handler( mergeBlocks( blockA.clientId, blockB.clientId ), { - dispatch, - getState, - } ); - expect( dispatch ).toHaveBeenCalledTimes( 2 ); - expect( dispatch ).toHaveBeenCalledWith( + const fulfillment = mergeBlocks( blockA.clientId, blockB.clientId ); + // MERGE_BLOCKS + fulfillment.next(); + // getBlock A + fulfillment.next(); + fulfillment.next( blockA ); + // getBlock B + fulfillment.next( blockB ); + // getSelectionStart + fulfillment.next( { + clientId: blockB.clientId, + attributeKey: 'content', + offset: 0, + } ); + // selectionChange + fulfillment.next( selectionChange( blockA.clientId, 'content', @@ -133,22 +127,19 @@ describe( 'effects', () => { 'chicken'.length + 1 ) ); - const lastCall = dispatch.mock.calls[ 1 ]; - expect( lastCall ).toHaveLength( 1 ); - const [ lastCallArgument ] = lastCall; - const expectedGenerator = replaceBlocks( - [ 'chicken', 'ribs' ], - [ + fulfillment.next(); + fulfillment.next(); + expect( fulfillment.next( blockA ).value ).toMatchObject( { + type: 'REPLACE_BLOCKS', + clientIds: [ 'chicken', 'ribs' ], + blocks: [ { clientId: 'chicken', name: 'core/test-block', attributes: { content: 'chicken ribs' }, }, - ] - ); - expect( Array.from( lastCallArgument ) ).toEqual( - Array.from( expectedGenerator ) - ); + ], + } ); } ); it( 'should not merge the blocks have different types without transformation', () => { @@ -181,23 +172,28 @@ describe( 'effects', () => { attributes: { content: 'ribs' }, innerBlocks: [], } ); - selectors.getBlock = ( state, clientId ) => { - return blockA.clientId === clientId ? blockA : blockB; - }; - const dispatch = jest.fn(); - const getState = () => ( { - selectionStart: { - clientId: blockB.clientId, - attributeKey: 'content', - offset: 0, - }, + + const fulfillment = mergeBlocks( blockA.clientId, blockB.clientId ); + // MERGE_BLOCKS + fulfillment.next(); + // getBlock A + fulfillment.next(); + fulfillment.next( blockA ); + // getBlock B + expect( fulfillment.next( blockB ).value ).toEqual( { + args: [], + selectorName: 'getSelectionStart', + storeKey: 'core/block-editor', + type: '@@data/SELECT', } ); - handler( mergeBlocks( blockA.clientId, blockB.clientId ), { - dispatch, - getState, + // getSelectionStart + const next = fulfillment.next( { + clientId: blockB.clientId, + attributeKey: 'content', + offset: 0, } ); - - expect( dispatch ).not.toHaveBeenCalled(); + expect( next.value ).toEqual( undefined ); + expect( next.done ).toBe( true ); } ); it( 'should transform and merge the blocks', () => { @@ -254,24 +250,27 @@ describe( 'effects', () => { attributes: { content2: 'ribs' }, innerBlocks: [], } ); - selectors.getBlock = ( state, clientId ) => { - return blockA.clientId === clientId ? blockA : blockB; - }; - const dispatch = jest.fn(); - const getState = () => ( { - selectionStart: { + + const fulfillment = mergeBlocks( blockA.clientId, blockB.clientId ); + // MERGE_BLOCKS + fulfillment.next(); + // getBlock A + fulfillment.next(); + fulfillment.next( blockA ); + // getBlock B + expect( fulfillment.next( blockB ).value ).toEqual( { + args: [], + selectorName: 'getSelectionStart', + storeKey: 'core/block-editor', + type: '@@data/SELECT', + } ); + expect( + fulfillment.next( { clientId: blockB.clientId, attributeKey: 'content2', offset: 0, - }, - } ); - handler( mergeBlocks( blockA.clientId, blockB.clientId ), { - dispatch, - getState, - } ); - - expect( dispatch ).toHaveBeenCalledTimes( 2 ); - expect( dispatch ).toHaveBeenCalledWith( + } ).value + ).toEqual( selectionChange( blockA.clientId, 'content', @@ -279,34 +278,32 @@ describe( 'effects', () => { 'chicken'.length + 1 ) ); - const expectedGenerator = replaceBlocks( - [ 'chicken', 'ribs' ], - [ + + fulfillment.next(); + fulfillment.next(); + fulfillment.next(); + expect( fulfillment.next( blockA ).value ).toMatchObject( { + type: 'REPLACE_BLOCKS', + clientIds: [ 'chicken', 'ribs' ], + blocks: [ { clientId: 'chicken', name: 'core/test-block', attributes: { content: 'chicken ribs' }, }, - ] - ); - const lastCall = dispatch.mock.calls[ 1 ]; - expect( lastCall ).toHaveLength( 1 ); - const [ lastCallArgument ] = lastCall; - expect( Array.from( lastCallArgument ) ).toEqual( - Array.from( expectedGenerator ) - ); + ], + } ); } ); } ); describe( 'validateBlocksToTemplate', () => { let store; beforeEach( () => { - store = createRegistry().registerStore( 'test', { + store = createRegistry().registerStore( 'core/block-editor', { actions, selectors, reducer, } ); - applyMiddlewares( store ); registerBlockType( 'core/test-block', defaultBlockSettings ); } ); @@ -317,31 +314,32 @@ describe( 'effects', () => { } ); } ); - it( 'should return undefined if no template assigned', () => { - const result = validateBlocksToTemplate( - resetBlocks( [ createBlock( 'core/test-block' ) ] ), - store + it( 'should return undefined if no template assigned', async () => { + const result = await store.dispatch( + validateBlocksToTemplate( + resetBlocks( [ createBlock( 'core/test-block' ) ] ), + store + ) ); - expect( result ).toBe( undefined ); + expect( result ).toEqual( undefined ); } ); - it( 'should return undefined if invalid but unlocked', () => { + it( 'should return undefined if invalid but unlocked', async () => { store.dispatch( updateSettings( { template: [ [ 'core/foo', {} ] ], } ) ); - const result = validateBlocksToTemplate( - resetBlocks( [ createBlock( 'core/test-block' ) ] ), - store + const result = await store.dispatch( + validateBlocksToTemplate( [ createBlock( 'core/test-block' ) ] ) ); - expect( result ).toBe( undefined ); + expect( result ).toEqual( undefined ); } ); - it( 'should return undefined if locked and valid', () => { + it( 'should return undefined if locked and valid', async () => { store.dispatch( updateSettings( { template: [ [ 'core/test-block' ] ], @@ -349,15 +347,14 @@ describe( 'effects', () => { } ) ); - const result = validateBlocksToTemplate( - resetBlocks( [ createBlock( 'core/test-block' ) ] ), - store + const result = await store.dispatch( + validateBlocksToTemplate( [ createBlock( 'core/test-block' ) ] ) ); - expect( result ).toBe( undefined ); + expect( result ).toEqual( undefined ); } ); - it( 'should return validity set action if invalid on default state', () => { + it( 'should return validity set action if invalid on default state', async () => { store.dispatch( updateSettings( { template: [ [ 'core/foo' ] ], @@ -365,12 +362,11 @@ describe( 'effects', () => { } ) ); - const result = validateBlocksToTemplate( - resetBlocks( [ createBlock( 'core/test-block' ) ] ), - store + const result = await store.dispatch( + validateBlocksToTemplate( [ createBlock( 'core/test-block' ) ] ) ); - expect( result ).toEqual( setTemplateValidity( false ) ); + expect( result ).toEqual( false ); } ); } ); } ); diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 8656f069347cfd..d11e53a9e74853 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -69,6 +69,7 @@ const { __experimentalGetLastBlockAttributeChanges, getLowestCommonAncestorWithSelectedBlock, __experimentalGetActiveBlockIdByBlockNames: getActiveBlockIdByBlockNames, + __experimentalGetParsedReusableBlock, } = selectors; describe( 'selectors', () => { @@ -3034,3 +3035,23 @@ describe( 'selectors', () => { } ); } ); } ); + +describe( '__experimentalGetParsedReusableBlock', () => { + const state = { + settings: { + __experimentalReusableBlocks: [ + { + id: 1, + content: { raw: '' }, + }, + ], + }, + }; + + // Regression test for https://github.com/WordPress/gutenberg/issues/26485. See https://github.com/WordPress/gutenberg/issues/26548. + it( "Should return an empty array if reusable block's content.raw is an empty string", () => { + expect( __experimentalGetParsedReusableBlock( state, 1 ) ).toEqual( + [] + ); + } ); +} ); diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 096eb3bacb7969..1ea21ed4574523 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -26,10 +26,12 @@ @import "./components/block-switcher/style.scss"; @import "./components/block-types-list/style.scss"; @import "./components/block-variation-picker/style.scss"; +@import "./components/block-variation-transforms/style.scss"; @import "./components/button-block-appender/style.scss"; @import "./components/colors-gradients/style.scss"; @import "./components/contrast-checker/style.scss"; @import "./components/default-block-appender/style.scss"; +@import "./components/font-appearance-control/style.scss"; @import "./components/link-control/style.scss"; @import "./components/line-height-control/style.scss"; @import "./components/image-size-control/style.scss"; @@ -43,11 +45,13 @@ @import "./components/rich-text/format-toolbar/style.scss"; @import "./components/rich-text/style.scss"; @import "./components/skip-to-selected-block/style.scss"; +@import "./components/text-decoration-and-transform/style.scss"; +@import "./components/text-transform-control/style.scss"; +@import "./components/text-decoration-control/style.scss"; @import "./components/tool-selector/style.scss"; @import "./components/url-input/style.scss"; @import "./components/url-popover/style.scss"; @import "./components/warning/style.scss"; -@import "./components/writing-flow/style.scss"; @import "./hooks/anchor.scss"; // This tag marks the end of the styles that apply to editing canvas contents and need to be manipulated when we resize the editor. diff --git a/packages/block-editor/src/utils/block-variation-transforms.js b/packages/block-editor/src/utils/block-variation-transforms.js new file mode 100644 index 00000000000000..db0901e6280127 --- /dev/null +++ b/packages/block-editor/src/utils/block-variation-transforms.js @@ -0,0 +1,31 @@ +/** + * External dependencies + */ +import { isMatch } from 'lodash'; + +/** @typedef {import('@wordpress/blocks').WPBlockVariation} WPBlockVariation */ + +/** + * Matches the provided block variations with a block's attributes. If no match + * or more than one matches are found it returns `undefined`. If a single match is + * found it returns it. + * + * This is a simple implementation for now as it takes into account only the attributes + * of a block variation and not `InnerBlocks`. + * + * @param {Object} blockAttributes - The block attributes to try to find a match. + * @param {WPBlockVariation[]} variations - A list of block variations to test for a match. + * @return {?WPBlockVariation} - If a match is found returns it. If not or more than one matches are found returns `undefined`. + */ +export const __experimentalGetMatchingVariation = ( + blockAttributes, + variations +) => { + if ( ! variations || ! blockAttributes ) return; + const matches = variations.filter( ( { attributes } ) => { + if ( ! attributes || ! Object.keys( attributes ).length ) return false; + return isMatch( blockAttributes, attributes ); + } ); + if ( matches.length !== 1 ) return; + return matches[ 0 ]; +}; diff --git a/packages/block-editor/src/utils/dom.js b/packages/block-editor/src/utils/dom.js index 89eec45cca5a5b..4f9c971ccd0771 100644 --- a/packages/block-editor/src/utils/dom.js +++ b/packages/block-editor/src/utils/dom.js @@ -3,24 +3,26 @@ * if exists. As much as possible, this helper should be avoided, and used only * in cases where isolated behaviors need remote access to a block node. * - * @param {string} clientId Block client ID. + * @param {string} clientId Block client ID. + * @param {Document} doc Document to search. * * @return {Element?} Block DOM node. */ -export function getBlockDOMNode( clientId ) { - return document.getElementById( 'block-' + clientId ); +export function getBlockDOMNode( clientId, doc ) { + return doc.getElementById( 'block-' + clientId ); } /** * Returns the preview container DOM node for a given block client ID, or * undefined if the container cannot be determined. * - * @param {string} clientId Block client ID. + * @param {string} clientId Block client ID. + * @param {Document} doc Document to search. * * @return {Node|undefined} Preview container DOM node. */ -export function getBlockPreviewContainerDOMNode( clientId ) { - const domNode = getBlockDOMNode( clientId ); +export function getBlockPreviewContainerDOMNode( clientId, doc ) { + const domNode = getBlockDOMNode( clientId, doc ); if ( ! domNode ) { return; diff --git a/packages/block-editor/src/utils/get-paste-event-data.js b/packages/block-editor/src/utils/get-paste-event-data.js index 7b798887c76b0e..0db158c5f6e8e6 100644 --- a/packages/block-editor/src/utils/get-paste-event-data.js +++ b/packages/block-editor/src/utils/get-paste-event-data.js @@ -1,21 +1,10 @@ -/** - * External dependencies - */ -import { find, isNil } from 'lodash'; - /** * WordPress dependencies */ import { createBlobURL } from '@wordpress/blob'; +import { getFilesFromDataTransfer } from '@wordpress/dom'; export function getPasteEventData( { clipboardData } ) { - let { items, files } = clipboardData; - - // In Edge these properties can be null instead of undefined, so a more - // rigorous test is required over using default values. - items = isNil( items ) ? [] : items; - files = isNil( files ) ? [] : files; - let plainText = ''; let html = ''; @@ -36,29 +25,9 @@ export function getPasteEventData( { clipboardData } ) { } } - files = Array.from( files ); - - Array.from( items ).forEach( ( item ) => { - if ( ! item.getAsFile ) { - return; - } - - const file = item.getAsFile(); - - if ( ! file ) { - return; - } - - const { name, type, size } = file; - - if ( ! find( files, { name, type, size } ) ) { - files.push( file ); - } - } ); - - files = files.filter( ( { type } ) => - /^image\/(?:jpe?g|png|gif)$/.test( type ) - ); + const files = getFilesFromDataTransfer( + clipboardData + ).filter( ( { type } ) => /^image\/(?:jpe?g|png|gif)$/.test( type ) ); // Only process files if no HTML is present. // A pasted file may have the URL as plain text. diff --git a/packages/block-editor/src/utils/index.js b/packages/block-editor/src/utils/index.js index c1ba2027f62065..f498a6bf4740bb 100644 --- a/packages/block-editor/src/utils/index.js +++ b/packages/block-editor/src/utils/index.js @@ -1,2 +1,3 @@ export { default as transformStyles } from './transform-styles'; export * from './theme'; +export * from './block-variation-transforms'; diff --git a/packages/block-editor/src/utils/test/block-variation-transforms.js b/packages/block-editor/src/utils/test/block-variation-transforms.js new file mode 100644 index 00000000000000..17c82bdb951329 --- /dev/null +++ b/packages/block-editor/src/utils/test/block-variation-transforms.js @@ -0,0 +1,70 @@ +/** + * Internal dependencies + */ +import { __experimentalGetMatchingVariation as getMatchingVariation } from '../block-variation-transforms'; + +describe( 'getMatchingVariation', () => { + describe( 'should not find a match', () => { + it( 'when no variations or attributes passed', () => { + expect( + getMatchingVariation( null, { content: 'hi' } ) + ).toBeUndefined(); + expect( getMatchingVariation( {} ) ).toBeUndefined(); + } ); + it( 'when no variation matched', () => { + const variations = [ + { name: 'one', attributes: { level: 1 } }, + { name: 'two', attributes: { level: 2 } }, + ]; + expect( + getMatchingVariation( { level: 4 }, variations ) + ).toBeUndefined(); + } ); + it( 'when more than one match found', () => { + const variations = [ + { name: 'one', attributes: { level: 1 } }, + { name: 'two', attributes: { level: 1, content: 'hi' } }, + ]; + expect( + getMatchingVariation( + { level: 1, content: 'hi', other: 'prop' }, + variations + ) + ).toBeUndefined(); + } ); + it( 'when variation is a superset of attributes', () => { + const variations = [ + { name: 'one', attributes: { level: 1, content: 'hi' } }, + ]; + expect( + getMatchingVariation( { level: 1, other: 'prop' }, variations ) + ).toBeUndefined(); + } ); + } ); + describe( 'should find a match', () => { + it( 'when variation has one attribute', () => { + const variations = [ + { name: 'one', attributes: { level: 1 } }, + { name: 'two', attributes: { level: 2 } }, + ]; + expect( + getMatchingVariation( + { level: 2, content: 'hi', other: 'prop' }, + variations + ).name + ).toEqual( 'two' ); + } ); + it( 'when variation has many attributes', () => { + const variations = [ + { name: 'one', attributes: { level: 1, content: 'hi' } }, + { name: 'two', attributes: { level: 2 } }, + ]; + expect( + getMatchingVariation( + { level: 1, content: 'hi', other: 'prop' }, + variations + ).name + ).toEqual( 'one' ); + } ); + } ); +} ); diff --git a/packages/block-editor/src/utils/transform-styles/index.js b/packages/block-editor/src/utils/transform-styles/index.js index b0f1d66fe55111..763554707bd2e7 100644 --- a/packages/block-editor/src/utils/transform-styles/index.js +++ b/packages/block-editor/src/utils/transform-styles/index.js @@ -23,20 +23,23 @@ import wrap from './transforms/wrap'; * @return {Array} converted rules. */ const transformStyles = ( styles, wrapperClassName = '' ) => { - return map( styles, ( { css, baseURL } ) => { - const transforms = []; - if ( wrapperClassName ) { - transforms.push( wrap( wrapperClassName ) ); - } - if ( baseURL ) { - transforms.push( urlRewrite( baseURL ) ); - } - if ( transforms.length ) { - return traverse( css, compose( transforms ) ); - } + return map( + styles, + ( { css, baseURL, __experimentalNoWrapper = false } ) => { + const transforms = []; + if ( wrapperClassName && ! __experimentalNoWrapper ) { + transforms.push( wrap( wrapperClassName ) ); + } + if ( baseURL ) { + transforms.push( urlRewrite( baseURL ) ); + } + if ( transforms.length ) { + return traverse( css, compose( transforms ) ); + } - return css; - } ); + return css; + } + ); }; export default transformStyles; diff --git a/packages/block-editor/tsconfig.json b/packages/block-editor/tsconfig.json index 55aa260a475945..66164083969d8b 100644 --- a/packages/block-editor/tsconfig.json +++ b/packages/block-editor/tsconfig.json @@ -5,13 +5,12 @@ "declarationDir": "build-types" }, "references": [ - { "path": "../element" } + { "path": "../deprecated" }, + { "path": "../element" }, + { "path": "../hooks" } ], // NOTE: This package is being progressively typed. You are encouraged to // expand this array with files which can be type-checked. At some point in // the future, this can be simplified to an `includes` of `src/**/*`. - "files": [ - "src/components/block-context/index.js", - "src/utils/dom.js" - ] + "files": [ "src/components/block-context/index.js", "src/utils/dom.js" ] } diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index 17307c36b03f1c..1261a02bf8de7b 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +### Enhancement + +- File Block: Copy url button is moved to Block toolbar. +- Code and Preformatted Blocks: delete on backspace from an empty block. + ### Bug Fixes - Fix a regression where the Cover would not show opacity controls for the default overlay color ([#26625](https://github.com/WordPress/gutenberg/pull/26625)). diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 8af95a525bc5c2..729e9f3784969a 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "2.26.4", + "version": "2.26.5", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -22,10 +22,11 @@ "module": "build-module/index.js", "react-native": "src/index", "sideEffects": [ - "build-style/**" + "build-style/**", + "src/**/*.scss" ], "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/a11y": "file:../a11y", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/autop": "file:../autop", diff --git a/packages/block-library/src/archives/block.json b/packages/block-library/src/archives/block.json index 7be2a90e41e8d1..d35f8b05001c4b 100644 --- a/packages/block-library/src/archives/block.json +++ b/packages/block-library/src/archives/block.json @@ -15,5 +15,6 @@ "supports": { "align": true, "html": false - } + }, + "editorStyle": "wp-block-archives-editor" } diff --git a/packages/block-library/src/archives/editor.scss b/packages/block-library/src/archives/editor.scss index 550b3c563d5f03..6bb1d96431acf7 100644 --- a/packages/block-library/src/archives/editor.scss +++ b/packages/block-library/src/archives/editor.scss @@ -1,3 +1,3 @@ -.block-editor ul.wp-block-archives { +ul.wp-block-archives { padding-left: 2.5em; } diff --git a/packages/block-library/src/audio/block.json b/packages/block-library/src/audio/block.json index a077767932e73c..c8e6e8e19d693e 100644 --- a/packages/block-library/src/audio/block.json +++ b/packages/block-library/src/audio/block.json @@ -39,5 +39,7 @@ "supports": { "anchor": true, "align": true - } + }, + "editorStyle": "wp-block-audio-editor", + "style": "wp-block-audio" } diff --git a/packages/block-library/src/audio/edit.js b/packages/block-library/src/audio/edit.js index 96a52d3f9d4a28..5f6e18dc09861d 100644 --- a/packages/block-library/src/audio/edit.js +++ b/packages/block-library/src/audio/edit.js @@ -186,6 +186,7 @@ function AudioEdit( { { ( ! RichText.isEmpty( caption ) || isSelected ) && ( diff --git a/packages/block-library/src/block/block.json b/packages/block-library/src/block/block.json index 97afcca2e594b0..aece916ab601ba 100644 --- a/packages/block-library/src/block/block.json +++ b/packages/block-library/src/block/block.json @@ -11,5 +11,6 @@ "customClassName": false, "html": false, "inserter": false - } + }, + "editorStyle": "wp-block-editor" } diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index af64ac7b80645f..1c7b9b039252ca 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -19,6 +19,8 @@ import { BlockControls, useBlockProps, } from '@wordpress/block-editor'; +import { store as noticesStore } from '@wordpress/notices'; +import { store as reusableBlocksStore } from '@wordpress/reusable-blocks'; /** * Internal dependencies @@ -51,16 +53,17 @@ export default function ReusableBlockEdit( { isSaving: select( 'core' ).isSavingEntityRecord( ...recordArgs ), canUserUpdate: select( 'core' ).canUser( 'update', 'blocks', ref ), isEditing: select( - 'core/reusable-blocks' + reusableBlocksStore ).__experimentalIsEditingReusableBlock( clientId ), settings: select( 'core/block-editor' ).getSettings(), } ), [ ref, clientId ] ); + const { clearSelectedBlock } = useDispatch( 'core/block-editor' ); const { editEntityRecord, saveEditedEntityRecord } = useDispatch( 'core' ); const { __experimentalSetEditingReusableBlock } = useDispatch( - 'core/reusable-blocks' + reusableBlocksStore ); const setIsEditing = useCallback( ( value ) => { @@ -71,10 +74,10 @@ export default function ReusableBlockEdit( { const { __experimentalConvertBlockToStatic: convertBlockToStatic, - } = useDispatch( 'core/reusable-blocks' ); + } = useDispatch( reusableBlocksStore ); const { createSuccessNotice, createErrorNotice } = useDispatch( - 'core/notices' + noticesStore ); const save = useCallback( async function () { try { @@ -117,6 +120,15 @@ export default function ReusableBlockEdit( { ); } + /** + * Clear the selected block when focus moves to the reusable block list. + * These blocks are in different stores and only one block should be + * selected at a time. + */ + function onFocus() { + clearSelectedBlock(); + } + let element = ( - - - +
+ + + +
); diff --git a/packages/block-library/src/block/editor.scss b/packages/block-library/src/block/editor.scss index b5c828e0680d5a..b3eb1a67e99459 100644 --- a/packages/block-library/src/block/editor.scss +++ b/packages/block-library/src/block/editor.scss @@ -1,8 +1,4 @@ .edit-post-visual-editor .block-library-block__reusable-block-container { - .block-editor-writing-flow__click-redirect { - min-height: auto; - } - // Unset the padding that root containers get when they're actually root containers. .is-root-container { padding-left: 0; diff --git a/packages/block-library/src/button/block.json b/packages/block-library/src/button/block.json index b0cff948360697..a5b4435271483b 100644 --- a/packages/block-library/src/button/block.json +++ b/packages/block-library/src/button/block.json @@ -52,6 +52,9 @@ }, "gradient": { "type": "string" + }, + "width": { + "type": "number" } }, "supports": { @@ -60,5 +63,7 @@ "alignWide": false, "reusable": false, "__experimentalSelector": ".wp-block-button > a" - } + }, + "editorStyle": "wp-block-button-editor", + "style": "wp-block-button" } diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 690913ea01632b..c7832ec7564202 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -9,6 +9,8 @@ import classnames from 'classnames'; import { __ } from '@wordpress/i18n'; import { useCallback, useState } from '@wordpress/element'; import { + Button, + ButtonGroup, KeyboardShortcuts, PanelBody, RangeControl, @@ -70,12 +72,42 @@ function BorderPanel( { borderRadius = '', setAttributes } ) { ); } +function WidthPanel( { selectedWidth, setAttributes } ) { + function handleChange( newWidth ) { + // Check if we are toggling the width off + const width = selectedWidth === newWidth ? undefined : newWidth; + + // Update attributes + setAttributes( { width } ); + } + + return ( + + + { [ 25, 50, 75, 100 ].map( ( widthValue ) => { + return ( + + ); + } ) } + + + ); +} + function URLPicker( { isSelected, url, setAttributes, opensInNewTab, onToggleOpenInNewTab, + anchorRef, } ) { const [ isURLPickerOpen, setIsURLPickerOpen ] = useState( false ); const urlIsSet = !! url; @@ -96,6 +128,7 @@ function URLPicker( { setIsURLPickerOpen( false ) } + anchorRef={ anchorRef?.current } > { @@ -202,8 +236,14 @@ function ButtonEdit( props ) { return ( <> -
+
setAttributes( { text: value } ) } @@ -239,12 +279,17 @@ function ButtonEdit( props ) { isSelected={ isSelected } opensInNewTab={ linkTarget === '_blank' } onToggleOpenInNewTab={ onToggleOpenInNewTab } + anchorRef={ blockProps.ref } /> + +
.wp-block-button { + &.has-custom-width { + max-width: none; + .wp-block-button__link { + width: 100%; + } + } + + &.wp-block-button__width-25 { + width: calc(25% - #{ $button-margin }); + } + + &.wp-block-button__width-50 { + width: calc(50% - #{ $button-margin }); + } + + &.wp-block-button__width-75 { + width: calc(75% - #{ $button-margin }); + } + + &.wp-block-button__width-100 { + width: calc(100% - #{ $button-margin }); + } +} + // the first selector is required for old buttons markup .wp-block-button.is-style-squared, .wp-block-button__link.wp-block-button.is-style-squared { @@ -41,7 +67,6 @@ $blocks-button__height: 3.1em; // the first selector is required for old buttons markup - .wp-block-button.no-border-radius, .wp-block-button__link.no-border-radius { border-radius: 0 !important; diff --git a/packages/block-library/src/buttons/block.json b/packages/block-library/src/buttons/block.json index a1e4c33c2ed3eb..704b3a33b29a90 100644 --- a/packages/block-library/src/buttons/block.json +++ b/packages/block-library/src/buttons/block.json @@ -2,9 +2,19 @@ "apiVersion": 2, "name": "core/buttons", "category": "design", + "attributes": { + "contentJustification": { + "type": "string" + }, + "orientation": { + "type": "string", + "default": "horizontal" + } + }, "supports": { "anchor": true, - "align": true, - "alignWide": false - } + "align": [ "wide", "full" ] + }, + "editorStyle": "wp-block-buttons-editor", + "style": "wp-block-buttons" } diff --git a/packages/block-library/src/buttons/content-justification-dropdown.js b/packages/block-library/src/buttons/content-justification-dropdown.js new file mode 100644 index 00000000000000..7437a8670941ec --- /dev/null +++ b/packages/block-library/src/buttons/content-justification-dropdown.js @@ -0,0 +1,73 @@ +/** + * WordPress dependencies + */ +import { DropdownMenu } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { + contentJustificationCenterIcon, + contentJustificationLeftIcon, + contentJustificationRightIcon, +} from './icons'; + +const DEFAULT_ALLOWED_VALUES = [ 'left', 'center', 'right' ]; + +const CONTROLS = { + left: { + icon: contentJustificationLeftIcon, + title: __( 'Justify content left' ), + }, + center: { + icon: contentJustificationCenterIcon, + title: __( 'Justify content center' ), + }, + right: { + icon: contentJustificationRightIcon, + title: __( 'Justify content right' ), + }, +}; + +const DEFAULT_ICON = CONTROLS.left.icon; + +/** + * Dropdown for selecting a content justification option. + * + * @param {Object} props Component props. + * @param {string[]} [props.allowedValues] List of options to include. Default: + * ['left', 'center', 'right']. + * @param {()=>void} props.onChange Callback to run when an option is + * selected in the dropdown. + * @param {Object} props.toggleProps Props to pass to the dropdown toggle. + * @param {string} props.value The current content justification + * value. + * + * @return {WPComponent} The component. + */ +export default function ContentJustificationDropdown( { + onChange, + allowedValues = DEFAULT_ALLOWED_VALUES, + toggleProps, + value, +} ) { + return ( + { + return { + ...CONTROLS[ allowedValue ], + isActive: value === allowedValue, + role: 'menuitemradio', + onClick: () => + onChange( + value === allowedValue ? undefined : allowedValue + ), + }; + } ) } + toggleProps={ toggleProps } + /> + ); +} diff --git a/packages/block-library/src/buttons/deprecated.js b/packages/block-library/src/buttons/deprecated.js new file mode 100644 index 00000000000000..47d049578ea58a --- /dev/null +++ b/packages/block-library/src/buttons/deprecated.js @@ -0,0 +1,38 @@ +/** + * WordPress dependencies + */ +import { InnerBlocks } from '@wordpress/block-editor'; + +const deprecated = [ + { + supports: { + align: [ 'center', 'left', 'right' ], + anchor: true, + }, + save() { + return ( +
+ +
+ ); + }, + isEligible( { align } ) { + return align && [ 'center', 'left', 'right' ].includes( align ); + }, + migrate( attributes ) { + return { + ...attributes, + align: undefined, + // Floating Buttons blocks shouldn't have been supported in the + // first place. Most users using them probably expected them to + // act like content justification controls, so these blocks are + // migrated to use content justification. + // As for center-aligned Buttons blocks, the content justification + // equivalent will create an identical end result in most cases. + contentJustification: attributes.align, + }; + }, + }, +]; + +export default deprecated; diff --git a/packages/block-library/src/buttons/edit.js b/packages/block-library/src/buttons/edit.js index 6cc9d69f8efb13..e6354ea1204dde 100644 --- a/packages/block-library/src/buttons/edit.js +++ b/packages/block-library/src/buttons/edit.js @@ -1,36 +1,68 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ import { - __experimentalAlignmentHookSettingsProvider as AlignmentHookSettingsProvider, - __experimentalUseInnerBlocksProps as useInnerBlocksProps, + BlockControls, useBlockProps, + __experimentalUseInnerBlocksProps as useInnerBlocksProps, } from '@wordpress/block-editor'; +import { ToolbarGroup, ToolbarItem } from '@wordpress/components'; /** * Internal dependencies */ -import { name as buttonBlockName } from '../button/'; +import { name as buttonBlockName } from '../button'; +import ContentJustificationDropdown from './content-justification-dropdown'; const ALLOWED_BLOCKS = [ buttonBlockName ]; const BUTTONS_TEMPLATE = [ [ 'core/button' ] ]; -// Inside buttons block alignment options are not supported. -const alignmentHooksSetting = { - isEmbedButton: true, -}; - -function ButtonsEdit() { - const blockProps = useBlockProps(); +function ButtonsEdit( { + attributes: { contentJustification, orientation }, + setAttributes, +} ) { + const blockProps = useBlockProps( { + className: classnames( { + [ `is-content-justification-${ contentJustification }` ]: contentJustification, + 'is-vertical': orientation === 'vertical', + } ), + } ); const innerBlocksProps = useInnerBlocksProps( blockProps, { allowedBlocks: ALLOWED_BLOCKS, template: BUTTONS_TEMPLATE, - orientation: 'horizontal', + orientation, + __experimentalLayout: { + type: 'default', + alignments: [], + }, + templateInsertUpdatesSelection: true, } ); return ( - + <> + + + + { ( toggleProps ) => ( + { + setAttributes( { + contentJustification: updatedValue, + } ); + } } + /> + ) } + + +
- + ); } diff --git a/packages/block-library/src/buttons/edit.native.js b/packages/block-library/src/buttons/edit.native.js index 307a8b19634aa7..1cbd6804df008c 100644 --- a/packages/block-library/src/buttons/edit.native.js +++ b/packages/block-library/src/buttons/edit.native.js @@ -7,28 +7,28 @@ import { View } from 'react-native'; /** * WordPress dependencies */ -import { - __experimentalAlignmentHookSettingsProvider as AlignmentHookSettingsProvider, - InnerBlocks, -} from '@wordpress/block-editor'; +import { BlockControls, InnerBlocks } from '@wordpress/block-editor'; import { createBlock } from '@wordpress/blocks'; import { useResizeObserver } from '@wordpress/compose'; import { useDispatch, useSelect } from '@wordpress/data'; import { useState, useEffect, useRef } from '@wordpress/element'; +import { ToolbarGroup, ToolbarItem } from '@wordpress/components'; /** * Internal dependencies */ import { name as buttonBlockName } from '../button/'; import styles from './editor.scss'; +import ContentJustificationDropdown from './content-justification-dropdown'; const ALLOWED_BLOCKS = [ buttonBlockName ]; const BUTTONS_TEMPLATE = [ [ 'core/button' ] ]; export default function ButtonsEdit( { - attributes: { align }, + attributes: { contentJustification }, clientId, isSelected, + setAttributes, } ) { const [ resizeObserver, sizes ] = useResizeObserver(); const [ maxWidth, setMaxWidth ] = useState( 0 ); @@ -88,6 +88,12 @@ export default function ButtonsEdit( { selectBlock( insertedBlock.clientId ); }, 200 ); + function onChangeContentJustification( updatedValue ) { + setAttributes( { + contentJustification: updatedValue, + } ); + } + const renderFooterAppender = useRef( () => ( ) ); - // Inside buttons block alignment options are not supported. - const alignmentHooksSetting = { - isEmbedButton: true, - }; - const shouldRenderFooterAppender = isSelected || isInnerButtonSelected; return ( - + <> + + + + { ( toggleProps ) => ( + + ) } + + + { resizeObserver } removeBlock( clientId ) : undefined } @@ -122,7 +136,9 @@ export default function ButtonsEdit( { parentWidth={ maxWidth } marginHorizontal={ spacing } marginVertical={ spacing } + __experimentalLayout={ { type: 'default', alignments: [] } } + templateInsertUpdatesSelection /> - + ); } diff --git a/packages/block-library/src/buttons/editor.scss b/packages/block-library/src/buttons/editor.scss index 121ba5f5af5cf2..e08d9923bb7728 100644 --- a/packages/block-library/src/buttons/editor.scss +++ b/packages/block-library/src/buttons/editor.scss @@ -1,20 +1,35 @@ -.wp-block-buttons > .wp-block { - // Override editor auto block margins. - margin-left: 0; +.wp-block > .wp-block-buttons { + display: flex; + flex-wrap: wrap; +} + +.wp-block-buttons { + > .wp-block { + // Override editor auto block margins. + margin-left: 0; + margin-top: $button-margin; + } + > .block-list-appender { + display: inline-flex; + align-items: center; + } + &.is-vertical { + > .block-list-appender .block-list-appender__toggle { + justify-content: flex-start; + } + } + > .wp-block-button { + &:focus { + box-shadow: none; + } + } } .wp-block[data-align="center"] > .wp-block-buttons { - display: flex; align-items: center; - flex-wrap: wrap; justify-content: center; } .wp-block[data-align="right"] > .wp-block-buttons { - display: flex; justify-content: flex-end; } - -.wp-block-buttons > .block-list-appender { - display: inline-block; -} diff --git a/packages/block-library/src/buttons/icons.js b/packages/block-library/src/buttons/icons.js new file mode 100644 index 00000000000000..38378b3afaa325 --- /dev/null +++ b/packages/block-library/src/buttons/icons.js @@ -0,0 +1,37 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; + +export const contentJustificationLeftIcon = ( + + + +); + +export const contentJustificationCenterIcon = ( + + + +); + +export const contentJustificationRightIcon = ( + + + +); diff --git a/packages/block-library/src/buttons/index.js b/packages/block-library/src/buttons/index.js index a9070759325a20..eac726ba4de305 100644 --- a/packages/block-library/src/buttons/index.js +++ b/packages/block-library/src/buttons/index.js @@ -7,10 +7,12 @@ import { button as icon } from '@wordpress/icons'; /** * Internal dependencies */ +import deprecated from './deprecated'; import transforms from './transforms'; import edit from './edit'; import metadata from './block.json'; import save from './save'; +import variations from './variations'; const { name } = metadata; @@ -35,7 +37,9 @@ export const settings = { }, ], }, + deprecated, transforms, edit, save, + variations, }; diff --git a/packages/block-library/src/buttons/save.js b/packages/block-library/src/buttons/save.js index 000acdcd4a6055..784833b63ca321 100644 --- a/packages/block-library/src/buttons/save.js +++ b/packages/block-library/src/buttons/save.js @@ -1,11 +1,25 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; -export default function save() { +export default function save( { + attributes: { contentJustification, orientation }, +} ) { return ( -
+
); diff --git a/packages/block-library/src/buttons/style.scss b/packages/block-library/src/buttons/style.scss index 45a0d6aed21b1b..64bac05b155b8f 100644 --- a/packages/block-library/src/buttons/style.scss +++ b/packages/block-library/src/buttons/style.scss @@ -1,37 +1,90 @@ -// Increased specificity to override blocks default margin. -.wp-block-buttons .wp-block-button { - display: inline-block; - margin-right: 0.5em; - margin-bottom: 0.5em; +.wp-block-buttons { + display: flex; + flex-direction: row; + flex-wrap: wrap; - &:last-child { - margin-right: 0; + &.is-vertical { + flex-direction: column; + > .wp-block-button { + /*rtl:ignore*/ + margin-right: 0; + &:last-child { + margin-bottom: 0; + } + } } -} -.wp-block-buttons.alignright .wp-block-button { - /*rtl:ignore*/ - margin-right: 0; - /*rtl:ignore*/ - margin-left: 0.5em; + // Increased specificity to override blocks default margin. + > .wp-block-button { + display: inline-block; + /*rtl:ignore*/ + margin-right: $button-margin; + margin-bottom: $button-margin; - &:first-child { - margin-left: 0; + &:last-child { + /*rtl:ignore*/ + margin-right: 0; + } } -} -.wp-block-buttons.alignleft .wp-block-button { - /*rtl:ignore*/ - margin-left: 0; - /*rtl:ignore*/ - margin-right: 0.5em; + &.is-content-justification-left { + justify-content: flex-start; + &.is-vertical { + align-items: flex-start; + } + } - &:last-child { - margin-right: 0; + &.is-content-justification-center { + justify-content: center; + &.is-vertical { + align-items: center; + } } -} -.wp-block-button.aligncenter, // This is to support the legacy Button block. -.wp-block-buttons.aligncenter { - text-align: center; + &.is-content-justification-right { + justify-content: flex-end; + + > .wp-block-button { + /*rtl:ignore*/ + margin-left: $button-margin; + /*rtl:ignore*/ + margin-right: 0; + + &:first-child { + /*rtl:ignore*/ + margin-left: 0; + } + } + + &.is-vertical { + align-items: flex-end; + } + } + + // Kept for backward compatibiity. + &.aligncenter { + text-align: center; + } + &.alignleft .wp-block-button { + /*rtl:ignore*/ + margin-left: 0; + /*rtl:ignore*/ + margin-right: $button-margin; + + &:last-child { + /*rtl:ignore*/ + margin-right: 0; + } + } + &.alignright .wp-block-button { + /*rtl:ignore*/ + margin-right: 0; + /*rtl:ignore*/ + margin-left: $button-margin; + + &:first-child { + /*rtl:ignore*/ + margin-left: 0; + } + } } diff --git a/packages/block-library/src/buttons/variations.js b/packages/block-library/src/buttons/variations.js new file mode 100644 index 00000000000000..438a5fc97b5ebe --- /dev/null +++ b/packages/block-library/src/buttons/variations.js @@ -0,0 +1,24 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +const variations = [ + { + name: 'buttons-horizontal', + isDefault: true, + title: __( 'Horizontal' ), + description: __( 'Buttons shown in a row.' ), + attributes: { orientation: 'horizontal' }, + scope: [ 'transform' ], + }, + { + name: 'buttons-vertical', + title: __( 'Vertical' ), + description: __( 'Buttons shown in a column.' ), + attributes: { orientation: 'vertical' }, + scope: [ 'transform' ], + }, +]; + +export default variations; diff --git a/packages/block-library/src/calendar/block.json b/packages/block-library/src/calendar/block.json index eb585dec70156c..edb73671e692a2 100644 --- a/packages/block-library/src/calendar/block.json +++ b/packages/block-library/src/calendar/block.json @@ -12,5 +12,6 @@ }, "supports": { "align": true - } + }, + "style": "wp-block-calendar" } diff --git a/packages/block-library/src/categories/block.json b/packages/block-library/src/categories/block.json index be8e9d1c213e74..5fe562622c83e3 100644 --- a/packages/block-library/src/categories/block.json +++ b/packages/block-library/src/categories/block.json @@ -19,5 +19,7 @@ "supports": { "align": true, "html": false - } + }, + "editorStyle": "wp-block-categories-editor", + "style": "wp-block-categories" } diff --git a/packages/block-library/src/categories/editor.scss b/packages/block-library/src/categories/editor.scss index 9b199c162746bf..cf112b6fc5ff63 100644 --- a/packages/block-library/src/categories/editor.scss +++ b/packages/block-library/src/categories/editor.scss @@ -1,4 +1,4 @@ -.block-editor .wp-block-categories ul { +.wp-block-categories ul { padding-left: 2.5em; ul { diff --git a/packages/block-library/src/code/block.json b/packages/block-library/src/code/block.json index d9f37ed061f808..0f29cf33cc0307 100644 --- a/packages/block-library/src/code/block.json +++ b/packages/block-library/src/code/block.json @@ -10,6 +10,8 @@ } }, "supports": { - "anchor": true - } + "anchor": true, + "fontSize": true + }, + "style": "wp-block-code" } diff --git a/packages/block-library/src/code/edit.js b/packages/block-library/src/code/edit.js index d86f46425de269..4153b14d753cdc 100644 --- a/packages/block-library/src/code/edit.js +++ b/packages/block-library/src/code/edit.js @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { RichText, useBlockProps } from '@wordpress/block-editor'; -export default function CodeEdit( { attributes, setAttributes } ) { +export default function CodeEdit( { attributes, setAttributes, onRemove } ) { const blockProps = useBlockProps(); return (
@@ -12,9 +12,11 @@ export default function CodeEdit( { attributes, setAttributes } ) {
 				tagName="code"
 				value={ attributes.content }
 				onChange={ ( content ) => setAttributes( { content } ) }
+				onRemove={ onRemove }
 				placeholder={ __( 'Write code…' ) }
 				aria-label={ __( 'Code' ) }
 				preserveWhiteSpace
+				__unstablePastePlainText
 			/>
 		
); diff --git a/packages/block-library/src/code/editor.scss b/packages/block-library/src/code/editor.scss deleted file mode 100644 index bc47a4f35ee9da..00000000000000 --- a/packages/block-library/src/code/editor.scss +++ /dev/null @@ -1,4 +0,0 @@ -.wp-block-code > code { - // PlainText cannot be an inline element yet. - display: block; -} diff --git a/packages/block-library/src/code/style.scss b/packages/block-library/src/code/style.scss new file mode 100644 index 00000000000000..f40e238f6b21bd --- /dev/null +++ b/packages/block-library/src/code/style.scss @@ -0,0 +1,10 @@ +// Provide a minimum of overflow handling. +.wp-block-code { + font-size: 0.9em; + + code { + display: block; + white-space: pre-wrap; + overflow-wrap: break-word; + } +} diff --git a/packages/block-library/src/code/theme.scss b/packages/block-library/src/code/theme.scss index be1469328db3dc..4dfc5a41a988e3 100644 --- a/packages/block-library/src/code/theme.scss +++ b/packages/block-library/src/code/theme.scss @@ -1,6 +1,5 @@ .wp-block-code { font-family: $editor-html-font; - font-size: 0.9em; color: $gray-900; padding: 0.8em 1em; border: 1px solid $gray-300; diff --git a/packages/block-library/src/column/column-preview.native.js b/packages/block-library/src/column/column-preview.native.js index 2b942be96dfe83..c18f96f5d453fb 100644 --- a/packages/block-library/src/column/column-preview.native.js +++ b/packages/block-library/src/column/column-preview.native.js @@ -7,33 +7,46 @@ import { View } from 'react-native'; * WordPress dependencies */ import { usePreferredColorSchemeStyle } from '@wordpress/compose'; +import { useConvertUnitToMobile } from '@wordpress/components'; /** * Internal dependencies */ import styles from './editor.scss'; -function ColumnsPreview( { columnWidths, selectedColumnIndex } ) { - const columnsPreviewStyle = usePreferredColorSchemeStyle( - styles.columnsPreview, - styles.columnsPreviewDark - ); - +function ColumnPreviewItem( { index, selectedColumnIndex, width } ) { const columnIndicatorStyle = usePreferredColorSchemeStyle( styles.columnIndicator, styles.columnIndicatorDark ); + const isSelectedColumn = index === selectedColumnIndex; + + const convertedWidth = useConvertUnitToMobile( width ); + return ( + + ); +} + +function ColumnsPreview( { columnWidths, selectedColumnIndex } ) { + const columnsPreviewStyle = usePreferredColorSchemeStyle( + styles.columnsPreview, + styles.columnsPreviewDark + ); return ( { columnWidths.map( ( width, index ) => { - const isSelectedColumn = index === selectedColumnIndex; return ( - ); diff --git a/packages/block-library/src/column/edit.js b/packages/block-library/src/column/edit.js index 12ddbd06e482d0..c3275d3667de49 100644 --- a/packages/block-library/src/column/edit.js +++ b/packages/block-library/src/column/edit.js @@ -21,6 +21,11 @@ import { import { useSelect, useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import { CSS_UNITS } from '../columns/utils'; + function ColumnEdit( { attributes: { verticalAlignment, width, templateLock = false }, setAttributes, @@ -87,13 +92,7 @@ function ColumnEdit( { 0 > parseFloat( nextWidth ) ? '0' : nextWidth; setAttributes( { width: nextWidth } ); } } - units={ [ - { value: '%', label: '%', default: '' }, - { value: 'px', label: 'px', default: '' }, - { value: 'em', label: 'em', default: '' }, - { value: 'rem', label: 'rem', default: '' }, - { value: 'vw', label: 'vw', default: '' }, - ] } + units={ CSS_UNITS } /> diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index b6d14f43ac701c..16e03fadc5fdd3 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -8,7 +8,7 @@ import { View } from 'react-native'; */ import { withSelect } from '@wordpress/data'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; -import { useEffect } from '@wordpress/element'; +import { useEffect, useState } from '@wordpress/element'; import { InnerBlocks, BlockControls, @@ -17,8 +17,9 @@ import { } from '@wordpress/block-editor'; import { PanelBody, - RangeControl, FooterMessageControl, + UnitControl, + getValueAndUnit, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; /** @@ -26,6 +27,7 @@ import { __ } from '@wordpress/i18n'; */ import styles from './editor.scss'; import ColumnsPreview from './column-preview'; +import { getWidths, getWidthWithUnit, CSS_UNITS } from '../columns/utils'; function ColumnEdit( { attributes, @@ -36,29 +38,46 @@ function ColumnEdit( { isParentSelected, contentStyle, columns, - columnCount, selectedColumnIndex, parentAlignment, } ) { - const { verticalAlignment } = attributes; + const { verticalAlignment, width } = attributes; + const { valueUnit = '%' } = getValueAndUnit( width ) || {}; + + const [ widthUnit, setWidthUnit ] = useState( valueUnit ); const updateAlignment = ( alignment ) => { setAttributes( { verticalAlignment: alignment } ); }; + useEffect( () => { + setWidthUnit( valueUnit ); + }, [ valueUnit ] ); + useEffect( () => { if ( ! verticalAlignment && parentAlignment ) { updateAlignment( parentAlignment ); } }, [] ); - const onWidthChange = ( width ) => { - setAttributes( { width: `${ width }%` } ); + const onChangeWidth = ( nextWidth ) => { + const widthWithUnit = getWidthWithUnit( nextWidth, widthUnit ); + + setAttributes( { + width: widthWithUnit, + } ); }; - const columnWidths = columns.map( - ( column ) => parseFloat( column.attributes.width ) || 100 / columnCount - ); + const onChangeUnit = ( nextUnit ) => { + setWidthUnit( nextUnit ); + const tempWidth = parseFloat( + width || getWidths( columns )[ selectedColumnIndex ] + ); + + setAttributes( { + width: getWidthWithUnit( tempWidth, nextUnit ), + } ); + }; if ( ! isSelected && ! hasChildren ) { return ( @@ -86,16 +105,19 @@ function ColumnEdit( { - } @@ -165,7 +187,6 @@ export default compose( [ const blockOrder = getBlockOrder( parentId ); const selectedColumnIndex = blockOrder.indexOf( clientId ); - const columnCount = getBlockCount( parentId ); const columns = getBlocks( parentId ); const parentAlignment = getBlockAttributes( parentId ) @@ -177,7 +198,6 @@ export default compose( [ isSelected, selectedColumnIndex, columns, - columnCount, parentAlignment, }; } ), diff --git a/packages/block-library/src/column/editor.native.scss b/packages/block-library/src/column/editor.native.scss index 3bf4ce4abc97d3..e5e2e853f59b73 100644 --- a/packages/block-library/src/column/editor.native.scss +++ b/packages/block-library/src/column/editor.native.scss @@ -40,6 +40,7 @@ min-width: 24px; border-color: $toolbar-button; flex-direction: row; + align-self: center; margin-right: 10px; border-width: $border-width; border-radius: $radius-block-ui; diff --git a/packages/block-library/src/columns/block.json b/packages/block-library/src/columns/block.json index 0cc93b718a89b1..7aa5ff3e1bc8c4 100644 --- a/packages/block-library/src/columns/block.json +++ b/packages/block-library/src/columns/block.json @@ -18,5 +18,7 @@ "gradients": true, "link": true } - } + }, + "editorStyle": "wp-block-columns-editor", + "style": "wp-block-columns" } diff --git a/packages/block-library/src/columns/edit.js b/packages/block-library/src/columns/edit.js index 6025e28e2da66d..9ad79099af20aa 100644 --- a/packages/block-library/src/columns/edit.js +++ b/packages/block-library/src/columns/edit.js @@ -22,6 +22,7 @@ import { withDispatch, useDispatch, useSelect } from '@wordpress/data'; import { createBlock, createBlocksFromInnerBlocksTemplate, + store as blocksStore, } from '@wordpress/blocks'; /** @@ -197,7 +198,7 @@ const ColumnsEditContainerWrapper = withDispatch( } } - replaceInnerBlocks( clientId, innerBlocks, false ); + replaceInnerBlocks( clientId, innerBlocks ); }, } ) )( ColumnsEditContainer ); @@ -209,7 +210,7 @@ function Placeholder( { clientId, name, setAttributes } ) { getBlockVariations, getBlockType, getDefaultBlockVariation, - } = select( 'core/blocks' ); + } = select( blocksStore ); return { blockType: getBlockType( name ), @@ -237,7 +238,8 @@ function Placeholder( { clientId, name, setAttributes } ) { clientId, createBlocksFromInnerBlocksTemplate( nextVariation.innerBlocks - ) + ), + true ); } } } diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 1e5e61bea8d9a9..453cf8f9eed3f0 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -12,6 +12,8 @@ import { PanelBody, RangeControl, FooterMessageControl, + UnitControl, + getValueAndUnit, } from '@wordpress/components'; import { InspectorControls, @@ -30,6 +32,15 @@ import { columns } from '@wordpress/icons'; */ import variations from './variations'; import styles from './editor.scss'; +import { + hasExplicitPercentColumnWidths, + getMappedColumnWidths, + getRedistributedColumnWidths, + toWidthPrecision, + getWidths, + getWidthWithUnit, + CSS_UNITS, +} from './utils'; import ColumnsPreview from '../column/column-preview'; /** @@ -148,32 +159,47 @@ function ColumnsEditContainer( { return null; }; - const getColumnsSliders = () => { - const columnWidths = innerColumns.map( - ( innerColumn ) => - parseFloat( innerColumn.attributes.width ) || 100 / columnCount - ); + const onChangeWidth = ( nextWidth, valueUnit, columnId ) => { + const widthWithUnit = getWidthWithUnit( nextWidth, valueUnit ); + + updateInnerColumnWidth( widthWithUnit, columnId ); + }; + + const onChangeUnit = ( nextUnit, index, columnId ) => { + const tempWidth = parseFloat( getWidths( innerColumns )[ index ] ); + const widthWithUnit = getWidthWithUnit( tempWidth, nextUnit ); + + updateInnerColumnWidth( widthWithUnit, columnId ); + }; + const getColumnsSliders = () => { return innerColumns.map( ( column, index ) => { + const { valueUnit = '%' } = + getValueAndUnit( column.attributes.width ) || {}; return ( - - updateInnerColumnWidth( value, column.clientId ) - } - cellContainerStyle={ styles.cellContainerStyle } + max={ valueUnit === '%' || ! valueUnit ? 100 : undefined } decimalNum={ 1 } - rangePreview={ + value={ getWidths( innerColumns )[ index ] } + onChange={ ( nextWidth ) => { + onChangeWidth( nextWidth, valueUnit, column.clientId ); + } } + onUnitChange={ ( nextUnit ) => + onChangeUnit( nextUnit, index, column.clientId ) + } + unit={ valueUnit } + units={ CSS_UNITS } + preview={ } - key={ column.clientId } - shouldDisplayTextInput={ false } /> ); } ); @@ -263,7 +289,7 @@ const ColumnsEditContainerWrapper = withDispatch( const { updateBlockAttributes } = dispatch( 'core/block-editor' ); updateBlockAttributes( columnId, { - width: `${ value }%`, + width: value, } ); }, updateBlockSettings( settings ) { @@ -286,15 +312,38 @@ const ColumnsEditContainerWrapper = withDispatch( ); let innerBlocks = getBlocks( clientId ); + const hasExplicitWidths = hasExplicitPercentColumnWidths( + innerBlocks + ); // Redistribute available width for existing inner blocks. const isAddingColumn = newColumns > previousColumns; - if ( isAddingColumn ) { - // Get verticalAlignment from Columns block to set the same to new Column - const { verticalAlignment } = - getBlockAttributes( clientId ) || {}; + // Get verticalAlignment from Columns block to set the same to new Column + const { verticalAlignment } = getBlockAttributes( clientId ) || {}; + + if ( isAddingColumn && hasExplicitWidths ) { + // If adding a new column, assign width to the new column equal to + // as if it were `1 / columns` of the total available space. + const newColumnWidth = toWidthPrecision( 100 / newColumns ); + // Redistribute in consideration of pending block insertion as + // constraining the available working width. + const widths = getRedistributedColumnWidths( + innerBlocks, + 100 - newColumnWidth + ); + + innerBlocks = [ + ...getMappedColumnWidths( innerBlocks, widths ), + ...times( newColumns - previousColumns, () => { + return createBlock( 'core/column', { + width: newColumnWidth, + verticalAlignment, + } ); + } ), + ]; + } else if ( isAddingColumn ) { innerBlocks = [ ...innerBlocks, ...times( newColumns - previousColumns, () => { @@ -309,9 +358,19 @@ const ColumnsEditContainerWrapper = withDispatch( innerBlocks, previousColumns - newColumns ); + + if ( hasExplicitWidths ) { + // Redistribute as if block is already removed. + const widths = getRedistributedColumnWidths( + innerBlocks, + 100 + ); + + innerBlocks = getMappedColumnWidths( innerBlocks, widths ); + } } - replaceInnerBlocks( clientId, innerBlocks, false ); + replaceInnerBlocks( clientId, innerBlocks ); }, onAddNextColumn: () => { const { clientId } = ownProps; @@ -348,20 +407,37 @@ const ColumnsEditContainerWrapper = withDispatch( const ColumnsEdit = ( props ) => { const { clientId, isSelected } = props; - const { columnCount, isDefaultColumns, innerColumns = [] } = useSelect( + const { + columnCount, + isDefaultColumns, + innerColumns = [], + hasParents, + parentBlockAlignment, + editorSidebarOpened, + } = useSelect( ( select ) => { - const { getBlockCount, getBlock } = select( 'core/block-editor' ); + const { + getBlockCount, + getBlock, + getBlockParents, + getBlockAttributes, + } = select( 'core/block-editor' ); + const { isEditorSidebarOpened } = select( 'core/edit-post' ); const block = getBlock( clientId ); const innerBlocks = block?.innerBlocks; const isContentEmpty = map( innerBlocks, ( innerBlock ) => innerBlock.innerBlocks.length ); + const parents = getBlockParents( clientId, true ); return { columnCount: getBlockCount( clientId ), isDefaultColumns: ! compact( isContentEmpty ).length, innerColumns: innerBlocks, + hasParents: !! parents.length, + parentBlockAlignment: getBlockAttributes( parents[ 0 ] )?.align, + editorSidebarOpened: isEditorSidebarOpened(), }; }, [ clientId ] @@ -380,6 +456,9 @@ const ColumnsEdit = ( props ) => { { describe( 'getRedistributedColumnWidths', () => { describe( 'explicit width', () => { - const blocks = [ + let blocks = [ { clientId: 'a', attributes: { width: 30 } }, { clientId: 'b', attributes: { width: 40 } }, ]; @@ -123,8 +123,8 @@ describe( 'getRedistributedColumnWidths', () => { const widths = getRedistributedColumnWidths( blocks, 60 ); expect( widths ).toEqual( { - a: 25, - b: 35, + a: 25.71, + b: 34.29, } ); } ); @@ -132,8 +132,36 @@ describe( 'getRedistributedColumnWidths', () => { const widths = getRedistributedColumnWidths( blocks, 80 ); expect( widths ).toEqual( { - a: 35, - b: 45, + a: 34.29, + b: 45.71, + } ); + } ); + + it( 'should decrease proportionally for third column', () => { + blocks = [ + { clientId: 'a', attributes: { width: 99 } }, + { clientId: 'b', attributes: { width: 1 } }, + ]; + const widths = getRedistributedColumnWidths( blocks, 66.67 ); + + expect( widths ).toEqual( { + a: 66, + b: 0.67, + } ); + } ); + + it( 'should decrease proportionally for fourth column', () => { + blocks = [ + { clientId: 'a', attributes: { width: 98 } }, + { clientId: 'b', attributes: { width: 1 } }, + { clientId: 'c', attributes: { width: 1 } }, + ]; + const widths = getRedistributedColumnWidths( blocks, 75 ); + + expect( widths ).toEqual( { + a: 73.5, + b: 0.75, + c: 0.75, } ); } ); } ); diff --git a/packages/block-library/src/columns/utils.js b/packages/block-library/src/columns/utils.js index 834e8b63e1f970..70fcc5841d41a0 100644 --- a/packages/block-library/src/columns/utils.js +++ b/packages/block-library/src/columns/utils.js @@ -3,6 +3,12 @@ */ import { sumBy, merge, mapValues } from 'lodash'; +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Platform } from '@wordpress/element'; + /** * Returns a column width attribute value rounded to standard precision. * Returns `undefined` if the value is not a valid finite number. @@ -83,12 +89,11 @@ export function getRedistributedColumnWidths( totalBlockCount = blocks.length ) { const totalWidth = getTotalColumnsWidth( blocks, totalBlockCount ); - const difference = availableWidth - totalWidth; - const adjustment = difference / blocks.length; - return mapValues( getColumnWidths( blocks, totalBlockCount ), ( width ) => - toWidthPrecision( width + adjustment ) - ); + return mapValues( getColumnWidths( blocks, totalBlockCount ), ( width ) => { + const newWidth = ( availableWidth * width ) / totalWidth; + return toWidthPrecision( newWidth ); + } ); } /** @@ -128,3 +133,68 @@ export function getMappedColumnWidths( blocks, widths ) { } ) ); } + +/** + * Returns an array with columns widths values, parsed or no depends on `withParsing` flag. + * + * @param {WPBlock[]} blocks Block objects. + * @param {?boolean} withParsing Whether value has to be parsed. + * + * @return {Array} Column widths. + */ +export function getWidths( blocks, withParsing = true ) { + return blocks.map( ( innerColumn ) => { + const innerColumnWidth = + innerColumn.attributes.width || 100 / blocks.length; + + return withParsing ? parseFloat( innerColumnWidth ) : innerColumnWidth; + } ); +} + +/** + * Returns a column width with unit. + * + * @param {string} width Column width. + * @param {string} unit Column width unit. + * + * @return {string} Column width with unit. + */ +export function getWidthWithUnit( width, unit ) { + width = 0 > parseFloat( width ) ? '0' : width; + + if ( unit === '%' ) { + width = Math.min( width, 100 ); + } + + return `${ width }${ unit }`; +} + +const isWeb = Platform.OS === 'web'; + +export const CSS_UNITS = [ + { + value: '%', + label: isWeb ? '%' : __( 'Percentage (%)' ), + default: '', + }, + { + value: 'px', + label: isWeb ? 'px' : __( 'Pixels (px)' ), + default: '', + }, + { + value: 'em', + label: isWeb ? 'em' : __( 'Relative to parent font size (em)' ), + default: '', + }, + { + value: 'rem', + label: isWeb ? 'rem' : __( 'Relative to root font size (rem)' ), + default: '', + }, + { + value: 'vw', + label: isWeb ? 'vw' : __( 'Viewport width (vw)' ), + default: '', + }, +]; diff --git a/packages/block-library/src/common.scss b/packages/block-library/src/common.scss new file mode 100644 index 00000000000000..bf437ce82cbb2f --- /dev/null +++ b/packages/block-library/src/common.scss @@ -0,0 +1,66 @@ +// The following selectors have increased specificity (using the :root prefix) +// to assure colors take effect over another base class color, mainly to let +// the colors override the added specificity by link states such as :hover. + +:root { + // Background colors. + @include background-colors(); + + // Foreground colors. + @include foreground-colors(); + + // Gradients + @include gradient-colors(); + + .has-link-color a { + color: var(--wp--style--color--link, #00e); + } +} + +// Font sizes. +.has-small-font-size { + font-size: 0.8125em; +} + +.has-regular-font-size, // Not used now, kept because of backward compatibility. +.has-normal-font-size { + font-size: 1em; +} + +.has-medium-font-size { + font-size: 1.25em; +} + +.has-large-font-size { + font-size: 2.25em; +} + +.has-larger-font-size, // Not used now, kept because of backward compatibility. +.has-huge-font-size { + font-size: 2.625em; +} + +// Text alignments. +.has-text-align-center { + text-align: center; +} + +.has-text-align-left { + /*rtl:ignore*/ + text-align: left; +} + +.has-text-align-right { + /*rtl:ignore*/ + text-align: right; +} + +// This tag marks the end of the styles that apply to editing canvas contents and need to be manipulated when we resize the editor. +#end-resizable-editor-section { + display: none; +} + +// Block alignments. +.aligncenter { + clear: both; +} diff --git a/packages/block-library/src/cover/block.json b/packages/block-library/src/cover/block.json index 29f1b17d7bb235..b0b2d58bde2aa8 100644 --- a/packages/block-library/src/cover/block.json +++ b/packages/block-library/src/cover/block.json @@ -57,5 +57,7 @@ "spacing": { "padding": true } - } + }, + "editorStyle": "wp-block-cover-editor", + "style": "wp-block-cover" } diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index ea6f30de8a91e4..00ea5c46633201 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -37,6 +37,7 @@ import { __experimentalPanelColorGradientSettings as PanelColorGradientSettings, __experimentalUnitControl as UnitControl, __experimentalBlockAlignmentMatrixToolbar as BlockAlignmentMatrixToolbar, + __experimentalBlockFullHeightAligmentToolbar as FullHeightAlignment, } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { withDispatch } from '@wordpress/data'; @@ -89,6 +90,7 @@ function CoverHeightInput( { value = '', } ) { const [ temporaryInput, setTemporaryInput ] = useState( null ); + const instanceId = useInstanceId( UnitControl ); const inputId = `block-cover-height-input-${ instanceId }`; const isPx = unit === 'px'; @@ -264,6 +266,39 @@ function CoverEdit( { const onSelectMedia = attributesFromMedia( setAttributes ); const isBlogUrl = isBlobURL( url ); + const [ prevMinHeightValue, setPrevMinHeightValue ] = useState( minHeight ); + const [ prevMinHeightUnit, setPrevMinHeightUnit ] = useState( + minHeightUnit + ); + const isMinFullHeight = minHeightUnit === 'vh' && minHeight === 100; + + const toggleMinFullHeight = () => { + if ( isMinFullHeight ) { + // If there aren't previous values, take the default ones. + if ( prevMinHeightUnit === 'vh' && prevMinHeightValue === 100 ) { + return setAttributes( { + minHeight: undefined, + minHeightUnit: undefined, + } ); + } + + // Set the previous values of height. + return setAttributes( { + minHeight: prevMinHeightValue, + minHeightUnit: prevMinHeightUnit, + } ); + } + + setPrevMinHeightValue( minHeight ); + setPrevMinHeightUnit( minHeightUnit ); + + // Set full height. + return setAttributes( { + minHeight: 100, + minHeightUnit: 'vh', + } ); + }; + const toggleParallax = () => { setAttributes( { hasParallax: ! hasParallax, @@ -322,6 +357,10 @@ function CoverEdit( { const controls = ( <> + { hasBackground && ( <> setAttributes( { minHeight: newMinHeight } ) } - onUnitChange={ ( nextUnit ) => { + onUnitChange={ ( nextUnit ) => setAttributes( { minHeightUnit: nextUnit, - } ); - } } + } ) + } /> { + const onOpacityChange = ( value ) => { setAttributes( { dimRatio: value } ); }; @@ -267,6 +278,16 @@ const Cover = ( { ); + const onChangeUnit = ( nextUnit ) => { + setAttributes( { + minHeightUnit: nextUnit, + minHeight: + nextUnit === 'px' + ? Math.max( CONTAINER_HEIGHT, COVER_MIN_HEIGHT ) + : CONTAINER_HEIGHT, + } ); + }; + const controls = ( ) : null } - @@ -512,9 +537,12 @@ const Cover = ( { - + diff --git a/packages/block-library/src/cover/shared.js b/packages/block-library/src/cover/shared.js index 08e56aff4b8dd6..a80ce7d28c8701 100644 --- a/packages/block-library/src/cover/shared.js +++ b/packages/block-library/src/cover/shared.js @@ -2,6 +2,8 @@ * WordPress dependencies */ import { getBlobTypeByURL, isBlobURL } from '@wordpress/blob'; +import { __ } from '@wordpress/i18n'; +import { Platform } from '@wordpress/element'; const POSITION_CLASSNAMES = { 'top left': 'is-position-top-left', @@ -23,12 +25,34 @@ export function backgroundImageStyles( url ) { return url ? { backgroundImage: `url(${ url })` } : {}; } +const isWeb = Platform.OS === 'web'; + export const CSS_UNITS = [ - { value: 'px', label: 'px', default: 430 }, - { value: 'em', label: 'em', default: 20 }, - { value: 'rem', label: 'rem', default: 20 }, - { value: 'vw', label: 'vw', default: 20 }, - { value: 'vh', label: 'vh', default: 50 }, + { + value: 'px', + label: isWeb ? 'px' : __( 'Pixels (px)' ), + default: '430', + }, + { + value: 'em', + label: isWeb ? 'em' : __( 'Relative to parent font size (em)' ), + default: '20', + }, + { + value: 'rem', + label: isWeb ? 'rem' : __( 'Relative to root font size (rem)' ), + default: '20', + }, + { + value: 'vw', + label: isWeb ? 'vw' : __( 'Viewport width (vw)' ), + default: '20', + }, + { + value: 'vh', + label: isWeb ? 'vh' : __( 'Viewport height (vh)' ), + default: '50', + }, ]; export function dimRatioToClass( ratio ) { diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index 1f7387b6b89bb1..0ece7da318dc6a 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -9,12 +9,11 @@ @import "./button/editor.scss"; @import "./buttons/editor.scss"; @import "./categories/editor.scss"; -@import "./code/editor.scss"; @import "./columns/editor.scss"; @import "./cover/editor.scss"; @import "./embed/editor.scss"; @import "./file/editor.scss"; -@import "./classic/editor.scss"; +@import "./freeform/editor.scss"; @import "./gallery/editor.scss"; @import "./group/editor.scss"; @import "./heading/editor.scss"; diff --git a/packages/block-library/src/embed/block.json b/packages/block-library/src/embed/block.json index 3f6531af5779f5..8c270a0c0c60e3 100644 --- a/packages/block-library/src/embed/block.json +++ b/packages/block-library/src/embed/block.json @@ -31,8 +31,8 @@ } }, "supports": { - "align": true, - "reusable": false, - "html": false - } + "align": true + }, + "editorStyle": "wp-block-embed-editor", + "style": "wp-block-embed" } diff --git a/packages/block-library/src/embed/editor.scss b/packages/block-library/src/embed/editor.scss index c4c30f1909e0dd..3535b8e9c1c3e5 100644 --- a/packages/block-library/src/embed/editor.scss +++ b/packages/block-library/src/embed/editor.scss @@ -45,3 +45,16 @@ bottom: 0; opacity: 0; } + +.wp-block[data-align="left"], +.wp-block[data-align="right"] { + > .wp-block-embed { + max-width: 360px; + width: 100%; + + // Unless these have a min-width, they collapse when floated. + .wp-block-embed__wrapper { + min-width: $break-zoomed-in; + } + } +} diff --git a/packages/block-library/src/embed/wp-embed-preview.js b/packages/block-library/src/embed/wp-embed-preview.js index 4b2c271c3c943a..b2609381fbbd9c 100644 --- a/packages/block-library/src/embed/wp-embed-preview.js +++ b/packages/block-library/src/embed/wp-embed-preview.js @@ -1,98 +1,98 @@ /** * WordPress dependencies */ -import { Component, createRef } from '@wordpress/element'; -import { withGlobalEvents } from '@wordpress/compose'; +import { useRef, useEffect, useMemo } from '@wordpress/element'; /** @typedef {import('@wordpress/element').WPSyntheticEvent} WPSyntheticEvent */ -/** - * Browser dependencies - */ +export default function WpEmbedPreview( { html } ) { + const ref = useRef(); + + useEffect( () => { + const { ownerDocument } = ref.current; + const { defaultView } = ownerDocument; + const { FocusEvent } = defaultView; + + /** + * Checks for WordPress embed events signaling the height change when iframe + * content loads or iframe's window is resized. The event is sent from + * WordPress core via the window.postMessage API. + * + * References: + * window.postMessage: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage + * WordPress core embed-template on load: https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-embed-template.js#L143 + * WordPress core embed-template on resize: https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-embed-template.js#L187 + * + * @param {WPSyntheticEvent} event Message event. + */ + function resizeWPembeds( { data: { secret, message, value } = {} } ) { + if ( + [ secret, message, value ].some( + ( attribute ) => ! attribute + ) || + message !== 'height' + ) { + return; + } -const { FocusEvent, DOMParser } = window; - -class WpEmbedPreview extends Component { - constructor() { - super( ...arguments ); - - this.checkFocus = this.checkFocus.bind( this ); - this.node = createRef(); - } - - componentDidMount() { - window.addEventListener( 'message', this.resizeWPembeds ); - } - - componentWillUnmount() { - window.removeEventListener( 'message', this.resizeWPembeds ); - } - - /** - * Checks for WordPress embed events signaling the height change when iframe - * content loads or iframe's window is resized. The event is sent from - * WordPress core via the window.postMessage API. - * - * References: - * window.postMessage: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage - * WordPress core embed-template on load: https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-embed-template.js#L143 - * WordPress core embed-template on resize: https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-embed-template.js#L187 - * - * @param {WPSyntheticEvent} event Message event. - */ - resizeWPembeds( { data: { secret, message, value } = {} } ) { - if ( - [ secret, message, value ].some( ( attribute ) => ! attribute ) || - message !== 'height' - ) { - return; + ownerDocument + .querySelectorAll( `iframe[data-secret="${ secret }"` ) + .forEach( ( iframe ) => { + if ( +iframe.height !== value ) { + iframe.height = value; + } + } ); } - document - .querySelectorAll( `iframe[data-secret="${ secret }"` ) - .forEach( ( iframe ) => { - if ( +iframe.height !== value ) { - iframe.height = value; - } - } ); - } - - /** - * Checks whether the wp embed iframe is the activeElement, - * if it is dispatch a focus event. - */ - checkFocus() { - const { activeElement } = document; - - if ( - activeElement.tagName !== 'IFRAME' || - activeElement.parentNode !== this.node.current - ) { - return; + /** + * Checks whether the wp embed iframe is the activeElement, + * if it is dispatch a focus event. + */ + function checkFocus() { + const { activeElement } = ownerDocument; + + if ( + activeElement.tagName !== 'IFRAME' || + activeElement.parentNode !== ref.current + ) { + return; + } + + const focusEvent = new FocusEvent( 'focus', { bubbles: true } ); + activeElement.dispatchEvent( focusEvent ); } - const focusEvent = new FocusEvent( 'focus', { bubbles: true } ); - activeElement.dispatchEvent( focusEvent ); - } + defaultView.addEventListener( 'message', resizeWPembeds ); + defaultView.addEventListener( 'blur', checkFocus ); - render() { - const { html } = this.props; - const doc = new DOMParser().parseFromString( html, 'text/html' ); + return () => { + defaultView.removeEventListener( 'message', resizeWPembeds ); + defaultView.removeEventListener( 'blur', checkFocus ); + }; + }, [] ); + + const __html = useMemo( () => { + const doc = new window.DOMParser().parseFromString( html, 'text/html' ); const iframe = doc.querySelector( 'iframe' ); - if ( iframe ) iframe.removeAttribute( 'style' ); + + if ( iframe ) { + iframe.removeAttribute( 'style' ); + } + const blockQuote = doc.querySelector( 'blockquote' ); - if ( blockQuote ) blockQuote.style.display = 'none'; - - return ( -
- ); - } -} -export default withGlobalEvents( { - blur: 'checkFocus', -} )( WpEmbedPreview ); + if ( blockQuote ) { + blockQuote.style.display = 'none'; + } + + return doc.body.innerHTML; + }, [ html ] ); + + return ( +
+ ); +} diff --git a/packages/block-library/src/file/block.json b/packages/block-library/src/file/block.json index 230942f76a6c36..ec42e3e31bf875 100644 --- a/packages/block-library/src/file/block.json +++ b/packages/block-library/src/file/block.json @@ -39,5 +39,7 @@ "supports": { "anchor": true, "align": true - } + }, + "editorStyle": "wp-block-file-editor", + "style": "wp-block-file" } diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index 0b645fb9bcb241..c2364b581133e7 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -8,9 +8,10 @@ import classnames from 'classnames'; */ import { getBlobByURL, isBlobURL, revokeBlobURL } from '@wordpress/blob'; import { - __unstableUseAnimate as useAnimate, - ClipboardButton, + __unstableGetAnimateClassName as getAnimateClassName, withNotices, + ToolbarGroup, + ToolbarButton, } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; import { @@ -21,7 +22,8 @@ import { RichText, useBlockProps, } from '@wordpress/block-editor'; -import { useEffect, useState } from '@wordpress/element'; +import { useEffect, useState, useRef } from '@wordpress/element'; +import { useCopyOnClick } from '@wordpress/compose'; import { __, _x } from '@wordpress/i18n'; import { file as icon } from '@wordpress/icons'; @@ -30,13 +32,22 @@ import { file as icon } from '@wordpress/icons'; */ import FileBlockInspector from './inspector'; -function FileEdit( { - isSelected, - attributes, - setAttributes, - noticeUI, - noticeOperations, -} ) { +function ClipboardToolbarButton( { text, disabled } ) { + const ref = useRef(); + const hasCopied = useCopyOnClick( ref, text ); + + return ( + + { hasCopied ? __( 'Copied!' ) : __( 'Copy URL' ) } + + ); +} + +function FileEdit( { attributes, setAttributes, noticeUI, noticeOperations } ) { const { id, fileName, @@ -47,7 +58,6 @@ function FileEdit( { downloadButtonText, } = attributes; const [ hasError, setHasError ] = useState( false ); - const [ showCopyConfirmation, setShowCopyConfirmation ] = useState( false ); const { media, mediaUpload } = useSelect( ( select ) => ( { media: @@ -82,10 +92,6 @@ function FileEdit( { } }, [] ); - useEffect( () => { - setShowCopyConfirmation( false ); - }, [ isSelected ] ); - function onSelectFile( newMedia ) { if ( newMedia && newMedia.url ) { setHasError( false ); @@ -104,14 +110,6 @@ function FileEdit( { noticeOperations.createErrorNotice( message ); } - function confirmCopyURL() { - setShowCopyConfirmation( true ); - } - - function resetCopyConfirmation() { - setShowCopyConfirmation( false ); - } - function changeLinkDestinationOption( newHref ) { // Choose Media File or Attachment Page (when file is in Media Library) setAttributes( { textLinkHref: newHref } ); @@ -131,7 +129,7 @@ function FileEdit( { const blockProps = useBlockProps( { className: classnames( - useAnimate( { type: isBlobURL( href ) ? 'loading' : null } ), + isBlobURL( href ) && getAnimateClassName( { type: 'loading' } ), { 'is-transient': isBlobURL( href ), } @@ -171,13 +169,19 @@ function FileEdit( { } } /> - + + + +
@@ -201,6 +205,7 @@ function FileEdit( { { /* Using RichText here instead of PlainText so that it can be styled like a button */ } ) }
- { isSelected && ( - - { showCopyConfirmation - ? __( 'Copied!' ) - : __( 'Copy URL' ) } - - ) }
); diff --git a/packages/block-library/src/file/edit.native.js b/packages/block-library/src/file/edit.native.js new file mode 100644 index 00000000000000..062270b322a58c --- /dev/null +++ b/packages/block-library/src/file/edit.native.js @@ -0,0 +1,599 @@ +/** + * External dependencies + */ +import { View, Clipboard, TouchableWithoutFeedback, Text } from 'react-native'; +import React from 'react'; + +/** + * WordPress dependencies + */ +import { + requestImageFailedRetryDialog, + requestImageUploadCancelDialog, + mediaUploadSync, +} from '@wordpress/react-native-bridge'; +import { + BlockIcon, + MediaPlaceholder, + MediaUploadProgress, + RichText, + PlainText, + BlockControls, + MediaUpload, + InspectorControls, + MEDIA_TYPE_ANY, +} from '@wordpress/block-editor'; +import { + ToolbarButton, + ToolbarGroup, + PanelBody, + ToggleControl, + BottomSheet, + SelectControl, + Icon, +} from '@wordpress/components'; +import { + file as icon, + replace, + button, + external, + link, + warning, +} from '@wordpress/icons'; +import { Component } from '@wordpress/element'; +import { __, _x } from '@wordpress/i18n'; +import { compose, withPreferredColorScheme } from '@wordpress/compose'; +import { withDispatch, withSelect } from '@wordpress/data'; +import { getProtocol } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; + +const URL_COPIED_NOTIFICATION_DURATION_MS = 1500; +const MIN_WIDTH = 40; + +export class FileEdit extends Component { + constructor( props ) { + super( props ); + + this.state = { + isUploadInProgress: false, + isSidebarLinkSettings: false, + placeholderTextWidth: 0, + maxWidth: 0, + }; + + this.timerRef = null; + + this.onLayout = this.onLayout.bind( this ); + this.onSelectFile = this.onSelectFile.bind( this ); + this.onChangeFileName = this.onChangeFileName.bind( this ); + this.onChangeDownloadButtonText = this.onChangeDownloadButtonText.bind( + this + ); + this.updateMediaProgress = this.updateMediaProgress.bind( this ); + this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( + this + ); + this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( + this + ); + this.getFileComponent = this.getFileComponent.bind( this ); + this.onChangeDownloadButtonVisibility = this.onChangeDownloadButtonVisibility.bind( + this + ); + this.onCopyURL = this.onCopyURL.bind( this ); + this.onChangeOpenInNewWindow = this.onChangeOpenInNewWindow.bind( + this + ); + + this.onChangeLinkDestinationOption = this.onChangeLinkDestinationOption.bind( + this + ); + this.onShowLinkSettings = this.onShowLinkSettings.bind( this ); + this.onFilePressed = this.onFilePressed.bind( this ); + this.mediaUploadStateReset = this.mediaUploadStateReset.bind( this ); + } + + componentDidMount() { + const { attributes, setAttributes } = this.props; + const { downloadButtonText } = attributes; + + if ( downloadButtonText === undefined || downloadButtonText === '' ) { + setAttributes( { + downloadButtonText: _x( 'Download', 'button label' ), + } ); + } + + if ( + attributes.id && + attributes.url && + getProtocol( attributes.url ) === 'file:' + ) { + mediaUploadSync(); + } + } + + componentWillUnmount() { + clearTimeout( this.timerRef ); + } + + componentDidUpdate( prevProps ) { + if ( + prevProps.isSidebarOpened && + ! this.props.isSidebarOpened && + this.state.isSidebarLinkSettings + ) { + this.setState( { isSidebarLinkSettings: false } ); + } + } + + onSelectFile( media ) { + this.props.setAttributes( { + href: media.url, + fileName: media.title, + textLinkHref: media.url, + id: media.id, + } ); + } + + onChangeFileName( fileName ) { + this.props.setAttributes( { fileName } ); + } + + onChangeDownloadButtonText( downloadButtonText ) { + this.props.setAttributes( { downloadButtonText } ); + } + + onChangeDownloadButtonVisibility( showDownloadButton ) { + this.props.setAttributes( { showDownloadButton } ); + } + + onChangeLinkDestinationOption( newHref ) { + // Choose Media File or Attachment Page (when file is in Media Library) + this.props.setAttributes( { textLinkHref: newHref } ); + } + + onCopyURL() { + if ( this.state.isUrlCopied ) { + return; + } + const { href } = this.props.attributes; + Clipboard.setString( href ); + + this.setState( { isUrlCopied: true } ); + this.timerRef = setTimeout( () => { + this.setState( { isUrlCopied: false } ); + }, URL_COPIED_NOTIFICATION_DURATION_MS ); + } + + onChangeOpenInNewWindow( newValue ) { + this.props.setAttributes( { + textLinkTarget: newValue ? '_blank' : false, + } ); + } + + updateMediaProgress( payload ) { + const { setAttributes } = this.props; + if ( payload.mediaUrl ) { + setAttributes( { url: payload.mediaUrl } ); + } + if ( ! this.state.isUploadInProgress ) { + this.setState( { isUploadInProgress: true } ); + } + } + + finishMediaUploadWithSuccess( payload ) { + const { setAttributes } = this.props; + + setAttributes( { + href: payload.mediaUrl, + id: payload.mediaServerId, + textLinkHref: payload.mediaUrl, + } ); + this.setState( { isUploadInProgress: false } ); + } + + finishMediaUploadWithFailure( payload ) { + this.props.setAttributes( { id: payload.mediaId } ); + this.setState( { isUploadInProgress: false } ); + } + + mediaUploadStateReset() { + const { setAttributes } = this.props; + + setAttributes( { + id: null, + href: null, + textLinkHref: null, + fileName: null, + } ); + this.setState( { isUploadInProgress: false } ); + } + + onShowLinkSettings() { + this.setState( + { + isSidebarLinkSettings: true, + }, + this.props.openSidebar + ); + } + + getToolbarEditButton( open ) { + return ( + + + + + + + ); + } + + getInspectorControls( + { showDownloadButton, textLinkTarget, href, textLinkHref }, + media, + isUploadInProgress, + isUploadFailed + ) { + let linkDestinationOptions = [ { value: href, label: __( 'URL' ) } ]; + const attachmentPage = media && media.link; + const { isSidebarLinkSettings } = this.state; + + if ( attachmentPage ) { + linkDestinationOptions = [ + { value: href, label: __( 'Media file' ) }, + { value: attachmentPage, label: __( 'Attachment page' ) }, + ]; + } + + const actionButtonStyle = this.props.getStylesFromColorScheme( + styles.actionButton, + styles.actionButtonDark + ); + + const isCopyUrlDisabled = isUploadFailed || isUploadInProgress; + const dimmedStyle = isCopyUrlDisabled && styles.disabledButton; + const finalButtonStyle = Object.assign( + {}, + actionButtonStyle, + dimmedStyle + ); + + return ( + + { isSidebarLinkSettings || ( + + ) } + + + + { ! isSidebarLinkSettings && ( + + ) } + + + + ); + } + + getStyleForAlignment( align ) { + const getFlexAlign = ( alignment ) => { + switch ( alignment ) { + case 'right': + return 'flex-end'; + case 'center': + return 'center'; + default: + return 'flex-start'; + } + }; + return { alignSelf: getFlexAlign( align ) }; + } + + getTextAlignmentForAlignment( align ) { + switch ( align ) { + case 'right': + return 'right'; + case 'center': + return 'center'; + default: + return 'left'; + } + } + + onFilePressed() { + const { attributes } = this.props; + + if ( this.state.isUploadInProgress ) { + requestImageUploadCancelDialog( attributes.id ); + } else if ( + attributes.id && + getProtocol( attributes.href ) === 'file:' + ) { + requestImageFailedRetryDialog( attributes.id ); + } + } + + onLayout( { nativeEvent } ) { + const { width } = nativeEvent.layout; + const { paddingLeft, paddingRight } = styles.defaultButton; + this.setState( { + maxWidth: width - ( paddingLeft + paddingRight ), + } ); + } + + // Render `Text` with `placeholderText` styled as a placeholder + // to calculate its width which then is set as a `minWidth` + // This should be fixed on RNAztec level. In the mean time, + // We use the same strategy implemented in Button block + getPlaceholderWidth( placeholderText ) { + const { maxWidth, placeholderTextWidth } = this.state; + return ( + { + const textWidth = + nativeEvent.lines[ 0 ] && nativeEvent.lines[ 0 ].width; + if ( textWidth && textWidth !== placeholderTextWidth ) { + this.setState( { + placeholderTextWidth: Math.min( + textWidth, + maxWidth + ), + } ); + } + } } + > + { placeholderText } + + ); + } + + getFileComponent( openMediaOptions, getMediaOptions ) { + const { attributes, media, isSelected } = this.props; + const { isButtonFocused, placeholderTextWidth } = this.state; + + const { + fileName, + downloadButtonText, + id, + showDownloadButton, + align, + } = attributes; + + const minWidth = + isButtonFocused || + ( ! isButtonFocused && + downloadButtonText && + downloadButtonText !== '' ) + ? MIN_WIDTH + : placeholderTextWidth; + + const placeholderText = + isButtonFocused || + ( ! isButtonFocused && + downloadButtonText && + downloadButtonText !== '' ) + ? '' + : __( 'Add text…' ); + + return ( + { + const dimmedStyle = + ( this.state.isUploadInProgress || isUploadFailed ) && + styles.disabledButton; + const finalButtonStyle = [ + styles.defaultButton, + dimmedStyle, + ]; + + const errorIconStyle = Object.assign( + {}, + styles.errorIcon, + styles.uploadFailed + ); + + return ( + + + { this.getPlaceholderWidth( placeholderText ) } + { isUploadInProgress || + this.getToolbarEditButton( + openMediaOptions + ) } + { getMediaOptions() } + { this.getInspectorControls( + attributes, + media, + isUploadInProgress, + isUploadFailed + ) } + + + { isUploadFailed && ( + + + + </View> + ) } + </View> + { showDownloadButton && + this.state.maxWidth > 0 && ( + <View + style={ [ + finalButtonStyle, + this.getStyleForAlignment( + align + ), + ] } + > + <RichText + withoutInteractiveFormatting + __unstableMobileNoFocusOnMount + rootTagsToEliminate={ [ 'p' ] } + tagName="p" + textAlign="center" + minWidth={ minWidth } + maxWidth={ this.state.maxWidth } + deleteEnter={ true } + style={ styles.buttonText } + value={ downloadButtonText } + placeholder={ placeholderText } + unstableOnFocus={ () => + this.setState( { + isButtonFocused: true, + } ) + } + onBlur={ () => + this.setState( { + isButtonFocused: false, + } ) + } + selectionColor={ + styles.buttonText.color + } + placeholderTextColor={ + styles.placeholderTextColor + .color + } + underlineColorAndroid="transparent" + onChange={ + this + .onChangeDownloadButtonText + } + /> + </View> + ) } + </View> + </TouchableWithoutFeedback> + ); + } } + /> + ); + } + + render() { + const { attributes } = this.props; + const { href } = attributes; + + if ( ! href ) { + return ( + <MediaPlaceholder + icon={ <BlockIcon icon={ icon } /> } + labels={ { + title: __( 'File' ), + instructions: __( 'CHOOSE A FILE' ), + } } + onSelect={ this.onSelectFile } + onFocus={ this.props.onFocus } + allowedTypes={ [ MEDIA_TYPE_ANY ] } + /> + ); + } + + return ( + <MediaUpload + allowedTypes={ [ MEDIA_TYPE_ANY ] } + isReplacingMedia={ true } + onSelect={ this.onSelectFile } + render={ ( { open, getMediaOptions } ) => { + return this.getFileComponent( open, getMediaOptions ); + } } + /> + ); + } +} + +export default compose( [ + withSelect( ( select, props ) => { + const { attributes } = props; + const { id, href } = attributes; + const { isEditorSidebarOpened } = select( 'core/edit-post' ); + const isNotFileHref = id && getProtocol( href ) !== 'file:'; + return { + media: isNotFileHref ? select( 'core' ).getMedia( id ) : undefined, + isSidebarOpened: isEditorSidebarOpened(), + }; + } ), + withDispatch( ( dispatch ) => { + const { openGeneralSidebar } = dispatch( 'core/edit-post' ); + return { + openSidebar: () => openGeneralSidebar( 'edit-post/block' ), + }; + } ), + withPreferredColorScheme, +] )( FileEdit ); diff --git a/packages/block-library/src/file/editor.scss b/packages/block-library/src/file/editor.scss index f24c18d3b4fd9c..ad7027ee663db7 100644 --- a/packages/block-library/src/file/editor.scss +++ b/packages/block-library/src/file/editor.scss @@ -21,8 +21,4 @@ display: inline-block; margin-left: 0.75em; } - - .wp-block-file__copy-url-button { - margin-left: 1em; - } } diff --git a/packages/block-library/src/file/style.native.scss b/packages/block-library/src/file/style.native.scss new file mode 100644 index 00000000000000..957956ba645d80 --- /dev/null +++ b/packages/block-library/src/file/style.native.scss @@ -0,0 +1,68 @@ +.container { + margin-top: 2px; +} + +.defaultButton { + border-radius: $border-width * 4; + padding: $grid-unit-10; + margin-top: $grid-unit-20; + background-color: $button-fallback-bg; +} + +.buttonText { + background-color: transparent; + color: $white; + padding: 0; + font-size: 16px; + padding-left: $grid-unit-20; + padding-right: $grid-unit-20; +} + +.disabledButton { + opacity: 0.45; +} + +.uploadFailedText { + color: #fff; + font-size: 14; + margin-top: 5; +} + +.retryContainer { + flex: 1; + background-color: "rgba(0, 0, 0, 0.5)"; +} + +.actionButton { + color: $blue-50; +} + +.actionButtonDark { + color: $blue-30; +} + +.errorContainer { + flex-direction: row; + align-items: center; + padding-top: 4px; +} + +.errorIcon { + margin-left: -4px; +} + +.uploadFailed { + padding: 0; + color: $alert-red; +} + +.placeholderTextColor { + color: rgba($color: $white, $alpha: 0.43); +} + +.placeholder { + font-family: $default-regular-font; + min-height: 22px; + font-size: 16px; + display: none; +} diff --git a/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap new file mode 100644 index 00000000000000..a4f88395a19e90 --- /dev/null +++ b/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap @@ -0,0 +1,423 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`File block renders file error state without crashing 1`] = ` +<View + pointerEvents="box-none" +> + <View + style={ + Array [ + undefined, + undefined, + ] + } + /> + <View + accessible={true} + focusable={true} + onClick={[Function]} + onLayout={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} + > + <Text + onTextLayout={[Function]} + style={ + Object { + "color": "gray", + } + } + > + + </Text> + Modal + <View + style={ + Object { + "height": 44, + } + } + > + <View> + <RCTAztecView + accessible={true} + activeFormats={Array []} + blockType={ + Object { + "tag": "p", + } + } + deleteEnter={true} + disableEditingMenu={false} + focusable={true} + fontFamily="serif" + isMultiline={false} + maxImagesWidth={200} + onBackspace={[Function]} + onBlur={[Function]} + onChange={[Function]} + onClick={[Function]} + onContentSizeChange={[Function]} + onEnter={[Function]} + onFocus={[Function]} + onHTMLContentWithCursor={[Function]} + onKeyDown={[Function]} + onPaste={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onSelectionChange={[Function]} + onStartShouldSetResponder={[Function]} + placeholder="File name" + placeholderTextColor="gray" + style={ + Object { + "backgroundColor": undefined, + "maxWidth": undefined, + "minHeight": 0, + } + } + text={ + Object { + "eventCount": undefined, + "linkTextColor": undefined, + "selection": null, + "text": "<p >File name</p>", + } + } + textAlign="left" + triggerKeyCodes={Array []} + /> + </View> + <View> + Svg + <TextInput + allowFontScaling={true} + editable={false} + fontFamily="serif" + onChange={[Function]} + rejectResponderTermination={true} + scrollEnabled={false} + style={ + Object { + "fontFamily": "serif", + } + } + underlineColorAndroid="transparent" + value="Error" + /> + </View> + </View> + <View + style={ + Array [ + Array [ + Object { + "paddingLeft": 10, + "paddingRight": 10, + }, + undefined, + ], + Object { + "alignSelf": "flex-start", + }, + ] + } + > + <View> + <RCTAztecView + accessible={true} + activeFormats={Array []} + blockType={ + Object { + "tag": "p", + } + } + color="white" + deleteEnter={true} + disableEditingMenu={false} + focusable={true} + fontFamily="serif" + isMultiline={false} + maxImagesWidth={200} + minWidth={40} + onBackspace={[Function]} + onBlur={[Function]} + onChange={[Function]} + onClick={[Function]} + onContentSizeChange={[Function]} + onEnter={[Function]} + onFocus={[Function]} + onHTMLContentWithCursor={[Function]} + onKeyDown={[Function]} + onPaste={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onSelectionChange={[Function]} + onStartShouldSetResponder={[Function]} + placeholder="" + placeholderTextColor="white" + selectionColor="white" + style={ + Object { + "backgroundColor": undefined, + "color": "white", + "maxWidth": 80, + "minHeight": 0, + } + } + text={ + Object { + "eventCount": undefined, + "linkTextColor": undefined, + "selection": null, + "text": "<p >Download</p>", + } + } + textAlign="center" + triggerKeyCodes={Array []} + /> + </View> + </View> + </View> +</View> +`; + +exports[`File block renders file without crashing 1`] = ` +<View + pointerEvents="box-none" +> + <View + style={ + Array [ + undefined, + undefined, + ] + } + /> + <View + accessible={true} + focusable={true} + onClick={[Function]} + onLayout={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} + > + <Text + onTextLayout={[Function]} + style={ + Object { + "color": "gray", + } + } + > + + </Text> + Modal + <View + style={ + Object { + "height": 44, + } + } + > + <View> + <RCTAztecView + accessible={true} + activeFormats={Array []} + blockType={ + Object { + "tag": "p", + } + } + deleteEnter={true} + disableEditingMenu={false} + focusable={true} + fontFamily="serif" + isMultiline={false} + maxImagesWidth={200} + onBackspace={[Function]} + onBlur={[Function]} + onChange={[Function]} + onClick={[Function]} + onContentSizeChange={[Function]} + onEnter={[Function]} + onFocus={[Function]} + onHTMLContentWithCursor={[Function]} + onKeyDown={[Function]} + onPaste={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onSelectionChange={[Function]} + onStartShouldSetResponder={[Function]} + placeholder="File name" + placeholderTextColor="gray" + style={ + Object { + "backgroundColor": undefined, + "maxWidth": undefined, + "minHeight": 0, + } + } + text={ + Object { + "eventCount": undefined, + "linkTextColor": undefined, + "selection": null, + "text": "<p >File name</p>", + } + } + textAlign="left" + triggerKeyCodes={Array []} + /> + </View> + </View> + <View + style={ + Array [ + Array [ + Object { + "paddingLeft": 10, + "paddingRight": 10, + }, + false, + ], + Object { + "alignSelf": "flex-start", + }, + ] + } + > + <View> + <RCTAztecView + accessible={true} + activeFormats={Array []} + blockType={ + Object { + "tag": "p", + } + } + color="white" + deleteEnter={true} + disableEditingMenu={false} + focusable={true} + fontFamily="serif" + isMultiline={false} + maxImagesWidth={200} + minWidth={40} + onBackspace={[Function]} + onBlur={[Function]} + onChange={[Function]} + onClick={[Function]} + onContentSizeChange={[Function]} + onEnter={[Function]} + onFocus={[Function]} + onHTMLContentWithCursor={[Function]} + onKeyDown={[Function]} + onPaste={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onSelectionChange={[Function]} + onStartShouldSetResponder={[Function]} + placeholder="" + placeholderTextColor="white" + selectionColor="white" + style={ + Object { + "backgroundColor": undefined, + "color": "white", + "maxWidth": 80, + "minHeight": 0, + } + } + text={ + Object { + "eventCount": undefined, + "linkTextColor": undefined, + "selection": null, + "text": "<p >Download</p>", + } + } + textAlign="center" + triggerKeyCodes={Array []} + /> + </View> + </View> + </View> +</View> +`; + +exports[`File block renders placeholder without crashing 1`] = ` +<View + style={ + Object { + "flex": 1, + } + } +> + <View + accessibilityHint="Double tap to select" + accessibilityLabel="File block. Empty" + accessibilityRole="button" + accessible={true} + focusable={true} + onClick={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} + style={ + Array [ + Array [ + undefined, + undefined, + undefined, + ], + undefined, + ] + } + > + Modal + <View + style={ + Object { + "fill": "gray", + } + } + > + <View + style={Object {}} + > + Svg + </View> + </View> + <Text> + File + </Text> + <Text> + CHOOSE A FILE + </Text> + </View> +</View> +`; diff --git a/packages/block-library/src/file/test/edit.native.js b/packages/block-library/src/file/test/edit.native.js new file mode 100644 index 00000000000000..c9d5453ce8f5b8 --- /dev/null +++ b/packages/block-library/src/file/test/edit.native.js @@ -0,0 +1,71 @@ +/** + * External dependencies + */ +import renderer from 'react-test-renderer'; + +/** + * WordPress dependencies + */ +import { MediaUploadProgress } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import { FileEdit } from '../edit.native.js'; + +const getTestComponentWithContent = ( attributes = {} ) => { + return renderer.create( + <FileEdit + attributes={ attributes } + setAttributes={ jest.fn() } + getMedia={ jest.fn() } + getStylesFromColorScheme={ jest.fn() } + /> + ); +}; + +describe( 'File block', () => { + it( 'renders placeholder without crashing', () => { + const component = getTestComponentWithContent(); + const rendered = component.toJSON(); + expect( rendered ).toMatchSnapshot(); + } ); + + it( 'renders file without crashing', () => { + const component = getTestComponentWithContent( { + showDownloadButton: true, + downloadButtonText: 'Download', + href: 'https://wordpress.org/latest.zip', + fileName: 'File name', + textLinkHref: 'https://wordpress.org/latest.zip', + id: '1', + } ); + + component + .getInstance() + .onLayout( { nativeEvent: { layout: { width: 100 } } } ); + + const rendered = component.toJSON(); + expect( rendered ).toMatchSnapshot(); + } ); + + it( 'renders file error state without crashing', () => { + const component = getTestComponentWithContent( { + showDownloadButton: true, + downloadButtonText: 'Download', + href: 'https://wordpress.org/latest.zip', + fileName: 'File name', + textLinkHref: 'https://wordpress.org/latest.zip', + id: '1', + } ); + component + .getInstance() + .onLayout( { nativeEvent: { layout: { width: 100 } } } ); + + const mediaUpload = component.root.findByType( MediaUploadProgress ); + mediaUpload.instance.finishMediaUploadWithFailure( { mediaId: -1 } ); + + const rendered = component.toJSON(); + expect( rendered ).toMatchSnapshot(); + } ); +} ); diff --git a/packages/block-library/src/classic/block.json b/packages/block-library/src/freeform/block.json similarity index 83% rename from packages/block-library/src/classic/block.json rename to packages/block-library/src/freeform/block.json index 0ee6ba171b3f81..a18cd84635004a 100644 --- a/packages/block-library/src/classic/block.json +++ b/packages/block-library/src/freeform/block.json @@ -12,5 +12,6 @@ "className": false, "customClassName": false, "reusable": false - } + }, + "editorStyle": "wp-block-freeform-editor" } diff --git a/packages/block-library/src/classic/convert-to-blocks-button.js b/packages/block-library/src/freeform/convert-to-blocks-button.js similarity index 100% rename from packages/block-library/src/classic/convert-to-blocks-button.js rename to packages/block-library/src/freeform/convert-to-blocks-button.js diff --git a/packages/block-library/src/classic/edit.js b/packages/block-library/src/freeform/edit.js similarity index 100% rename from packages/block-library/src/classic/edit.js rename to packages/block-library/src/freeform/edit.js diff --git a/packages/block-library/src/classic/edit.native.js b/packages/block-library/src/freeform/edit.native.js similarity index 100% rename from packages/block-library/src/classic/edit.native.js rename to packages/block-library/src/freeform/edit.native.js diff --git a/packages/block-library/src/classic/editor.scss b/packages/block-library/src/freeform/editor.scss similarity index 100% rename from packages/block-library/src/classic/editor.scss rename to packages/block-library/src/freeform/editor.scss diff --git a/packages/block-library/src/classic/index.js b/packages/block-library/src/freeform/index.js similarity index 100% rename from packages/block-library/src/classic/index.js rename to packages/block-library/src/freeform/index.js diff --git a/packages/block-library/src/classic/save.js b/packages/block-library/src/freeform/save.js similarity index 100% rename from packages/block-library/src/classic/save.js rename to packages/block-library/src/freeform/save.js diff --git a/packages/block-library/src/gallery/block.json b/packages/block-library/src/gallery/block.json index ffdbb1d51981cf..8beac02827f612 100644 --- a/packages/block-library/src/gallery/block.json +++ b/packages/block-library/src/gallery/block.json @@ -1,4 +1,5 @@ { + "apiVersion": 2, "name": "core/gallery", "category": "media", "attributes": { @@ -78,5 +79,7 @@ "supports": { "anchor": true, "align": true - } + }, + "editorStyle": "wp-block-gallery-editor", + "style": "wp-block-gallery" } diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 8e5c2c7bc8336e..598f12ae2dd619 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -25,12 +25,17 @@ import { withNotices, RangeControl, } from '@wordpress/components'; -import { MediaPlaceholder, InspectorControls } from '@wordpress/block-editor'; -import { Platform, useEffect, useState } from '@wordpress/element'; +import { + MediaPlaceholder, + InspectorControls, + useBlockProps, +} from '@wordpress/block-editor'; +import { Platform, useEffect, useState, useMemo } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { getBlobByURL, isBlobURL, revokeBlobURL } from '@wordpress/blob'; import { useDispatch, withSelect } from '@wordpress/data'; import { withViewportMatch } from '@wordpress/viewport'; +import { View } from '@wordpress/primitives'; /** * Internal dependencies @@ -67,7 +72,6 @@ const MOBILE_CONTROL_PROPS_RANGE_CONTROL = Platform.select( { function GalleryEdit( props ) { const { attributes, - className, isSelected, noticeUI, noticeOperations, @@ -323,7 +327,6 @@ function GalleryEdit( props ) { <MediaPlaceholder addToGallery={ hasImages } isAppender={ hasImages } - className={ className } disableMediaButtons={ hasImages && ! isSelected } icon={ ! hasImages && sharedIcon } labels={ { @@ -341,8 +344,10 @@ function GalleryEdit( props ) { /> ); + const blockProps = useBlockProps(); + if ( ! hasImages ) { - return mediaPlaceholder; + return <View { ...blockProps }>{ mediaPlaceholder }</View>; } const imageSizeOptions = getImagesSizeOptions(); @@ -397,6 +402,7 @@ function GalleryEdit( props ) { onDeselectImage={ onDeselectImage } onSetImageAttributes={ setImageAttributes } onFocusGalleryCaption={ onFocusGalleryCaption } + blockProps={ blockProps } /> </> ); @@ -408,45 +414,47 @@ export default compose( [ const { getSettings } = select( 'core/block-editor' ); const { imageSizes, mediaUpload } = getSettings(); - let resizedImages = {}; - - if ( isSelected ) { - resizedImages = reduce( - ids, - ( currentResizedImages, id ) => { - if ( ! id ) { - return currentResizedImages; - } - const image = getMedia( id ); - const sizes = reduce( - imageSizes, - ( currentSizes, size ) => { - const defaultUrl = get( image, [ - 'sizes', - size.slug, - 'url', - ] ); - const mediaDetailsUrl = get( image, [ - 'media_details', - 'sizes', - size.slug, - 'source_url', - ] ); - return { - ...currentSizes, - [ size.slug ]: defaultUrl || mediaDetailsUrl, - }; - }, - {} - ); - return { - ...currentResizedImages, - [ parseInt( id, 10 ) ]: sizes, - }; - }, - {} - ); - } + const resizedImages = useMemo( () => { + if ( isSelected ) { + return reduce( + ids, + ( currentResizedImages, id ) => { + if ( ! id ) { + return currentResizedImages; + } + const image = getMedia( id ); + const sizes = reduce( + imageSizes, + ( currentSizes, size ) => { + const defaultUrl = get( image, [ + 'sizes', + size.slug, + 'url', + ] ); + const mediaDetailsUrl = get( image, [ + 'media_details', + 'sizes', + size.slug, + 'source_url', + ] ); + return { + ...currentSizes, + [ size.slug ]: + defaultUrl || mediaDetailsUrl, + }; + }, + {} + ); + return { + ...currentResizedImages, + [ parseInt( id, 10 ) ]: sizes, + }; + }, + {} + ); + } + return {}; + }, [ isSelected, ids, imageSizes ] ); return { imageSizes, diff --git a/packages/block-library/src/gallery/editor.scss b/packages/block-library/src/gallery/editor.scss index 2205dfdb1e52f1..c67ce5bf3778d2 100644 --- a/packages/block-library/src/gallery/editor.scss +++ b/packages/block-library/src/gallery/editor.scss @@ -7,16 +7,11 @@ } // @todo: this deserves a refactor, by being moved to the toolbar. - .block-editor-media-placeholder { - margin-bottom: $grid-unit-15; - padding: $grid-unit-15; - - // This element is empty here anyway. + .block-editor-media-placeholder.is-appender { .components-placeholder__label { display: none; } - - .components-button { + .block-editor-media-placeholder__button { margin-bottom: 0; } } diff --git a/packages/block-library/src/gallery/gallery-image.js b/packages/block-library/src/gallery/gallery-image.js index ac77851911a441..4dac396d289e0e 100644 --- a/packages/block-library/src/gallery/gallery-image.js +++ b/packages/block-library/src/gallery/gallery-image.js @@ -83,7 +83,7 @@ class GalleryImage extends Component { onRemoveImage( event ) { if ( - this.container === document.activeElement && + this.container === this.container.ownerDocument.activeElement && this.props.isSelected && [ BACKSPACE, DELETE ].indexOf( event.keyCode ) !== -1 ) { @@ -275,6 +275,7 @@ class GalleryImage extends Component { { ! isEditing && ( isSelected || caption ) && ( <RichText tagName="figcaption" + aria-label={ __( 'Image caption text' ) } placeholder={ isSelected ? __( 'Write caption…' ) : null } diff --git a/packages/block-library/src/gallery/gallery.js b/packages/block-library/src/gallery/gallery.js index 354d0d17cb616a..dc261bbf1e4699 100644 --- a/packages/block-library/src/gallery/gallery.js +++ b/packages/block-library/src/gallery/gallery.js @@ -20,7 +20,6 @@ import { defaultColumnsNumber } from './shared'; export const Gallery = ( props ) => { const { attributes, - className, isSelected, setAttributes, selectedImage, @@ -33,6 +32,7 @@ export const Gallery = ( props ) => { onSetImageAttributes, onFocusGalleryCaption, insertBlocksAfter, + blockProps, } = props; const { @@ -45,7 +45,8 @@ export const Gallery = ( props ) => { return ( <figure - className={ classnames( className, { + { ...blockProps } + className={ classnames( blockProps.className, { [ `align${ align }` ]: align, [ `columns-${ columns }` ]: columns, 'is-cropped': imageCrop, @@ -95,6 +96,7 @@ export const Gallery = ( props ) => { isHidden={ ! isSelected && RichText.isEmpty( caption ) } tagName="figcaption" className="blocks-gallery-caption" + aria-label={ __( 'Gallery caption text' ) } placeholder={ __( 'Write gallery caption…' ) } value={ caption } unstableOnFocus={ onFocusGalleryCaption } diff --git a/packages/block-library/src/gallery/save.js b/packages/block-library/src/gallery/save.js index 87478c82fbf625..0c51884a5db957 100644 --- a/packages/block-library/src/gallery/save.js +++ b/packages/block-library/src/gallery/save.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { RichText } from '@wordpress/block-editor'; +import { RichText, useBlockProps } from '@wordpress/block-editor'; /** * Internal dependencies @@ -20,13 +20,10 @@ export default function save( { attributes } ) { caption, linkTo, } = attributes; + const className = `columns-${ columns } ${ imageCrop ? 'is-cropped' : '' }`; return ( - <figure - className={ `columns-${ columns } ${ - imageCrop ? 'is-cropped' : '' - }` } - > + <figure { ...useBlockProps.save( { className } ) }> <ul className="blocks-gallery-grid"> { images.map( ( image ) => { let href; diff --git a/packages/block-library/src/group/block.json b/packages/block-library/src/group/block.json index 6014985192acfe..e0be130edc00a6 100644 --- a/packages/block-library/src/group/block.json +++ b/packages/block-library/src/group/block.json @@ -25,5 +25,7 @@ "spacing": { "padding": true } - } + }, + "editorStyle": "wp-block-group-editor", + "style": "wp-block-group" } diff --git a/packages/block-library/src/group/edit.native.js b/packages/block-library/src/group/edit.native.js index 2cb3a15e6aa123..2ec026b420a632 100644 --- a/packages/block-library/src/group/edit.native.js +++ b/packages/block-library/src/group/edit.native.js @@ -8,7 +8,7 @@ import { View } from 'react-native'; */ import { withSelect } from '@wordpress/data'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; -import { InnerBlocks, withColors } from '@wordpress/block-editor'; +import { InnerBlocks } from '@wordpress/block-editor'; import { useCallback } from '@wordpress/element'; import { WIDE_ALIGNMENTS } from '@wordpress/components'; @@ -21,7 +21,9 @@ function GroupEdit( { attributes, hasInnerBlocks, isSelected, + isLastInnerBlockSelected, getStylesFromColorScheme, + mergedStyle, } ) { const { align } = attributes; const isFullWidth = align === WIDE_ALIGNMENTS.alignments.full; @@ -64,6 +66,14 @@ function GroupEdit( { ! hasInnerBlocks && isFullWidth && styles.fullWidth, + mergedStyle, + isSelected && + hasInnerBlocks && + mergedStyle?.backgroundColor && + styles.hasBackgroundAppender, + isLastInnerBlockSelected && + mergedStyle?.backgroundColor && + styles.isLastInnerBlockSelected, ] } > <InnerBlocks renderAppender={ isSelected && renderAppender } /> @@ -72,14 +82,31 @@ function GroupEdit( { } export default compose( [ - withColors( 'backgroundColor' ), withSelect( ( select, { clientId } ) => { - const { getBlock } = select( 'core/block-editor' ); + const { + getBlock, + getBlockIndex, + hasSelectedInnerBlock, + getSelectedBlockClientId, + } = select( 'core/block-editor' ); const block = getBlock( clientId ); + const hasInnerBlocks = !! ( block && block.innerBlocks.length ); + const isInnerBlockSelected = + hasInnerBlocks && hasSelectedInnerBlock( clientId, true ); + let isLastInnerBlockSelected = false; + + if ( isInnerBlockSelected ) { + const { innerBlocks } = block; + const selectedBlockClientId = getSelectedBlockClientId(); + const totalInnerBlocks = innerBlocks.length - 1; + const blockIndex = getBlockIndex( selectedBlockClientId, clientId ); + isLastInnerBlockSelected = totalInnerBlocks === blockIndex; + } return { - hasInnerBlocks: !! ( block && block.innerBlocks.length ), + hasInnerBlocks, + isLastInnerBlockSelected, }; } ), withPreferredColorScheme, diff --git a/packages/block-library/src/group/editor.native.scss b/packages/block-library/src/group/editor.native.scss index 4015bcf95bf46f..d33982b2b36bf9 100644 --- a/packages/block-library/src/group/editor.native.scss +++ b/packages/block-library/src/group/editor.native.scss @@ -36,3 +36,11 @@ margin-left: $block-edge-to-content; margin-right: $block-edge-to-content; } + +.hasBackgroundAppender { + padding-bottom: $grid-unit-30; +} + +.isLastInnerBlockSelected { + padding-bottom: 0; +} diff --git a/packages/block-library/src/group/theme.scss b/packages/block-library/src/group/theme.scss index 61e711c3aaa2f8..2aa1b4f5ade7e1 100644 --- a/packages/block-library/src/group/theme.scss +++ b/packages/block-library/src/group/theme.scss @@ -1,8 +1,7 @@ .wp-block-group { &.has-background { // Matches paragraph Block padding - // Todo: normalise with variables - padding: 20px 30px; + padding: $block-bg-padding--v $block-bg-padding--h; margin-top: 0; margin-bottom: 0; } diff --git a/packages/block-library/src/heading/block.json b/packages/block-library/src/heading/block.json index 50bf37cdaefa5d..6cd496431f799d 100644 --- a/packages/block-library/src/heading/block.json +++ b/packages/block-library/src/heading/block.json @@ -30,13 +30,51 @@ "fontSize": true, "lineHeight": true, "__experimentalSelector": { - "core/heading/h1": "h1", - "core/heading/h2": "h2", - "core/heading/h3": "h3", - "core/heading/h4": "h4", - "core/heading/h5": "h5", - "core/heading/h6": "h6" + "core/heading/h1": { + "selector": "h1", + "title": "h1", + "attributes": { + "level": 1 + } + }, + "core/heading/h2": { + "selector": "h2", + "title": "h2", + "attributes": { + "level": 2 + } + }, + "core/heading/h3": { + "selector": "h3", + "title": "h3", + "attributes": { + "level": 3 + } + }, + "core/heading/h4": { + "selector": "h4", + "title": "h4", + "attributes": { + "level": 4 + } + }, + "core/heading/h5": { + "selector": "h5", + "title": "h5", + "attributes": { + "level": 5 + } + }, + "core/heading/h6": { + "selector": "h6", + "title": "h6", + "attributes": { + "level": 6 + } + } }, "__unstablePasteTextInline": true - } + }, + "editorStyle": "wp-block-heading-editor", + "style": "wp-block-heading" } diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index ed15802b0bb493..f798d8501b3e13 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -73,6 +73,7 @@ function HeadingEdit( { } } onReplace={ onReplace } onRemove={ () => onReplace( [] ) } + aria-label={ __( 'Heading text' ) } placeholder={ placeholder || __( 'Write heading…' ) } textAlign={ textAlign } { ...blockProps } diff --git a/packages/block-library/src/heading/transforms.js b/packages/block-library/src/heading/transforms.js index 25c3fc4fa7f1c2..8e6768c0d19d8a 100644 --- a/packages/block-library/src/heading/transforms.js +++ b/packages/block-library/src/heading/transforms.js @@ -67,6 +67,16 @@ const transforms = { } ); }, } ) ), + ...[ 1, 2, 3, 4, 5, 6 ].map( ( level ) => ( { + type: 'enter', + regExp: new RegExp( `^/(h|H)${ level }$` ), + transform( content ) { + return createBlock( name, { + level, + content, + } ); + }, + } ) ), ], to: [ { diff --git a/packages/block-library/src/html/block.json b/packages/block-library/src/html/block.json index 13aa611346e07e..266b4511e0fe5b 100644 --- a/packages/block-library/src/html/block.json +++ b/packages/block-library/src/html/block.json @@ -12,5 +12,6 @@ "customClassName": false, "className": false, "html": false - } + }, + "editorStyle": "wp-block-html-editor" } diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index ec80dc4c9ed250..02cfd21dfd9cd5 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -72,5 +72,7 @@ }, "supports": { "anchor": true - } + }, + "editorStyle": "wp-block-image-editor", + "style": "wp-block-image" } diff --git a/packages/block-library/src/image/editor.scss b/packages/block-library/src/image/editor.scss index a8ead3186e057d..563f68b9541c70 100644 --- a/packages/block-library/src/image/editor.scss +++ b/packages/block-library/src/image/editor.scss @@ -106,6 +106,7 @@ figure.wp-block-image:not(.wp-block) { .wp-block-image__zoom { .components-popover__content { overflow: visible; + min-width: 260px; } .components-range-control { @@ -115,6 +116,8 @@ figure.wp-block-image:not(.wp-block) { .components-base-control__field { display: flex; margin-bottom: 0; + flex-direction: column; + align-items: flex-start; } } diff --git a/packages/block-library/src/image/image-editing/aspect-ratio-dropdown.js b/packages/block-library/src/image/image-editing/aspect-ratio-dropdown.js new file mode 100644 index 00000000000000..978df291303099 --- /dev/null +++ b/packages/block-library/src/image/image-editing/aspect-ratio-dropdown.js @@ -0,0 +1,129 @@ +/** + * WordPress dependencies + */ +import { check, aspectRatio as aspectRatioIcon } from '@wordpress/icons'; +import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { POPOVER_PROPS } from './constants'; +import { useImageEditingContext } from './context'; + +function AspectGroup( { aspectRatios, isDisabled, label, onClick, value } ) { + return ( + <MenuGroup label={ label }> + { aspectRatios.map( ( { title, aspect } ) => ( + <MenuItem + key={ aspect } + disabled={ isDisabled } + onClick={ () => { + onClick( aspect ); + } } + role="menuitemradio" + isSelected={ aspect === value } + icon={ aspect === value ? check : undefined } + > + { title } + </MenuItem> + ) ) } + </MenuGroup> + ); +} + +export default function AspectRatioDropdown( { toggleProps } ) { + const { + isInProgress, + aspect, + setAspect, + defaultAspect, + } = useImageEditingContext(); + + return ( + <DropdownMenu + icon={ aspectRatioIcon } + label={ __( 'Aspect Ratio' ) } + popoverProps={ POPOVER_PROPS } + toggleProps={ toggleProps } + className="wp-block-image__aspect-ratio" + > + { ( { onClose } ) => ( + <> + <AspectGroup + isDisabled={ isInProgress } + onClick={ ( newAspect ) => { + setAspect( newAspect ); + onClose(); + } } + value={ aspect } + aspectRatios={ [ + { + title: __( 'Original' ), + aspect: defaultAspect, + }, + { + title: __( 'Square' ), + aspect: 1, + }, + ] } + /> + <AspectGroup + label={ __( 'Landscape' ) } + isDisabled={ isInProgress } + onClick={ ( newAspect ) => { + setAspect( newAspect ); + onClose(); + } } + value={ aspect } + aspectRatios={ [ + { + title: __( '16:10' ), + aspect: 16 / 10, + }, + { + title: __( '16:9' ), + aspect: 16 / 9, + }, + { + title: __( '4:3' ), + aspect: 4 / 3, + }, + { + title: __( '3:2' ), + aspect: 3 / 2, + }, + ] } + /> + <AspectGroup + label={ __( 'Portrait' ) } + isDisabled={ isInProgress } + onClick={ ( newAspect ) => { + setAspect( newAspect ); + onClose(); + } } + value={ aspect } + aspectRatios={ [ + { + title: __( '10:16' ), + aspect: 10 / 16, + }, + { + title: __( '9:16' ), + aspect: 9 / 16, + }, + { + title: __( '3:4' ), + aspect: 3 / 4, + }, + { + title: __( '2:3' ), + aspect: 2 / 3, + }, + ] } + /> + </> + ) } + </DropdownMenu> + ); +} diff --git a/packages/block-library/src/image/image-editing/constants.js b/packages/block-library/src/image/image-editing/constants.js new file mode 100644 index 00000000000000..0692a76895ca1f --- /dev/null +++ b/packages/block-library/src/image/image-editing/constants.js @@ -0,0 +1,6 @@ +export const MIN_ZOOM = 100; +export const MAX_ZOOM = 300; +export const POPOVER_PROPS = { + position: 'bottom right', + isAlternate: true, +}; diff --git a/packages/block-library/src/image/image-editing/context.js b/packages/block-library/src/image/image-editing/context.js new file mode 100644 index 00000000000000..ed11045c819da9 --- /dev/null +++ b/packages/block-library/src/image/image-editing/context.js @@ -0,0 +1,56 @@ +/** + * WordPress dependencies + */ +import { createContext, useContext, useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import useSaveImage from './use-save-image'; +import useTransformImage from './use-transform-image'; + +const ImageEditingContext = createContext( {} ); + +export const useImageEditingContext = () => useContext( ImageEditingContext ); + +export default function ImageEditingProvider( { + id, + url, + naturalWidth, + naturalHeight, + isEditing, + onFinishEditing, + onSaveImage, + children, +} ) { + const transformImage = useTransformImage( + { + url, + naturalWidth, + naturalHeight, + }, + isEditing + ); + + const saveImage = useSaveImage( { + id, + url, + onSaveImage, + onFinishEditing, + ...transformImage, + } ); + + const providerValue = useMemo( + () => ( { + ...transformImage, + ...saveImage, + } ), + [ transformImage, saveImage ] + ); + + return ( + <ImageEditingContext.Provider value={ providerValue }> + { children } + </ImageEditingContext.Provider> + ); +} diff --git a/packages/block-library/src/image/image-editing/cropper.js b/packages/block-library/src/image/image-editing/cropper.js new file mode 100644 index 00000000000000..24e56573455f9f --- /dev/null +++ b/packages/block-library/src/image/image-editing/cropper.js @@ -0,0 +1,74 @@ +/** + * External dependencies + */ +import Cropper from 'react-easy-crop'; +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { Spinner } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { MIN_ZOOM, MAX_ZOOM } from './constants'; + +import { useImageEditingContext } from './context'; + +export default function ImageCropper( { + url, + width, + height, + clientWidth, + naturalHeight, + naturalWidth, +} ) { + const { + isInProgress, + editedUrl, + position, + zoom, + aspect, + setPosition, + setCrop, + setZoom, + rotation, + } = useImageEditingContext(); + + let editedHeight = height || ( clientWidth * naturalHeight ) / naturalWidth; + + if ( rotation % 180 === 90 ) { + editedHeight = ( clientWidth * naturalWidth ) / naturalHeight; + } + + return ( + <div + className={ classnames( 'wp-block-image__crop-area', { + 'is-applying': isInProgress, + } ) } + style={ { + width, + height: editedHeight, + } } + > + <Cropper + image={ editedUrl || url } + disabled={ isInProgress } + minZoom={ MIN_ZOOM / 100 } + maxZoom={ MAX_ZOOM / 100 } + crop={ position } + zoom={ zoom / 100 } + aspect={ aspect } + onCropChange={ setPosition } + onCropComplete={ ( newCropPercent ) => { + setCrop( newCropPercent ); + } } + onZoomChange={ ( newZoom ) => { + setZoom( newZoom * 100 ); + } } + /> + { isInProgress && <Spinner /> } + </div> + ); +} diff --git a/packages/block-library/src/image/image-editing/form-controls.js b/packages/block-library/src/image/image-editing/form-controls.js new file mode 100644 index 00000000000000..50bb37e08b5eb1 --- /dev/null +++ b/packages/block-library/src/image/image-editing/form-controls.js @@ -0,0 +1,22 @@ +/** + * WordPress dependencies + */ +import { ToolbarButton } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { useImageEditingContext } from './context'; + +export default function FormControls() { + const { isInProgress, apply, cancel } = useImageEditingContext(); + return ( + <> + <ToolbarButton onClick={ apply } disabled={ isInProgress }> + { __( 'Apply' ) } + </ToolbarButton> + <ToolbarButton onClick={ cancel }>{ __( 'Cancel' ) }</ToolbarButton> + </> + ); +} diff --git a/packages/block-library/src/image/image-editing/index.js b/packages/block-library/src/image/image-editing/index.js new file mode 100644 index 00000000000000..223c291f191aae --- /dev/null +++ b/packages/block-library/src/image/image-editing/index.js @@ -0,0 +1,54 @@ +/** + * WordPress dependencies + */ +import { BlockControls } from '@wordpress/block-editor'; +import { ToolbarGroup, ToolbarItem } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import Cropper from './cropper'; +import ZoomDropdown from './zoom-dropdown'; +import AspectRatioDropdown from './aspect-ratio-dropdown'; +import RotationButton from './rotation-button'; +import FormControls from './form-controls'; + +export default function ImageEditor( { + url, + width, + height, + clientWidth, + naturalHeight, + naturalWidth, +} ) { + return ( + <> + <Cropper + url={ url } + width={ width } + height={ height } + clientWidth={ clientWidth } + naturalHeight={ naturalHeight } + naturalWidth={ naturalWidth } + /> + <BlockControls> + <ToolbarGroup> + <ZoomDropdown /> + <ToolbarItem> + { ( toggleProps ) => ( + <AspectRatioDropdown toggleProps={ toggleProps } /> + ) } + </ToolbarItem> + </ToolbarGroup> + <ToolbarGroup> + <RotationButton /> + </ToolbarGroup> + <ToolbarGroup> + <FormControls /> + </ToolbarGroup> + </BlockControls> + </> + ); +} + +export { default as ImageEditingProvider } from './context'; diff --git a/packages/block-library/src/image/image-editing/rotation-button.js b/packages/block-library/src/image/image-editing/rotation-button.js new file mode 100644 index 00000000000000..b29108f72e7e9c --- /dev/null +++ b/packages/block-library/src/image/image-editing/rotation-button.js @@ -0,0 +1,24 @@ +/** + * WordPress dependencies + */ + +import { ToolbarButton } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { rotateRight as rotateRightIcon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import { useImageEditingContext } from './context'; + +export default function RotationButton() { + const { isInProgress, rotateClockwise } = useImageEditingContext(); + return ( + <ToolbarButton + icon={ rotateRightIcon } + label={ __( 'Rotate' ) } + onClick={ rotateClockwise } + disabled={ isInProgress } + /> + ); +} diff --git a/packages/block-library/src/image/image-editing/use-save-image.js b/packages/block-library/src/image/image-editing/use-save-image.js new file mode 100644 index 00000000000000..3f95f45a2abfca --- /dev/null +++ b/packages/block-library/src/image/image-editing/use-save-image.js @@ -0,0 +1,97 @@ +/** + * WordPress dependencies + */ +import apiFetch from '@wordpress/api-fetch'; +import { useDispatch } from '@wordpress/data'; +import { useCallback, useMemo, useState } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; +import { store as noticesStore } from '@wordpress/notices'; + +export default function useSaveImage( { + crop, + rotation, + height, + width, + aspect, + url, + id, + onSaveImage, + onFinishEditing, +} ) { + const { createErrorNotice } = useDispatch( noticesStore ); + const [ isInProgress, setIsInProgress ] = useState( false ); + + const cancel = useCallback( () => { + setIsInProgress( false ); + onFinishEditing(); + }, [ setIsInProgress, onFinishEditing ] ); + + const apply = useCallback( () => { + setIsInProgress( true ); + + let attrs = {}; + + // The crop script may return some very small, sub-pixel values when the image was not cropped. + // Crop only when the new size has changed by more than 0.1%. + if ( crop.width < 99.9 || crop.height < 99.9 ) { + attrs = crop; + } + + if ( rotation > 0 ) { + attrs.rotation = rotation; + } + + attrs.src = url; + + apiFetch( { + path: `/wp/v2/media/${ id }/edit`, + method: 'POST', + data: attrs, + } ) + .then( ( response ) => { + onSaveImage( { + id: response.id, + url: response.source_url, + height: height && width ? width / aspect : undefined, + } ); + } ) + .catch( ( error ) => { + createErrorNotice( + sprintf( + /* translators: 1. Error message */ + __( 'Could not edit image. %s' ), + error.message + ), + { + id: 'image-editing-error', + type: 'snackbar', + } + ); + } ) + .finally( () => { + setIsInProgress( false ); + onFinishEditing(); + } ); + }, [ + setIsInProgress, + crop, + rotation, + height, + width, + aspect, + url, + onSaveImage, + createErrorNotice, + setIsInProgress, + onFinishEditing, + ] ); + + return useMemo( + () => ( { + isInProgress, + apply, + cancel, + } ), + [ isInProgress, apply, cancel ] + ); +} diff --git a/packages/block-library/src/image/image-editing/use-transform-image.js b/packages/block-library/src/image/image-editing/use-transform-image.js new file mode 100644 index 00000000000000..4ce55a921f9ced --- /dev/null +++ b/packages/block-library/src/image/image-editing/use-transform-image.js @@ -0,0 +1,152 @@ +/** + * WordPress dependencies + */ +import { useCallback, useEffect, useMemo, useState } from '@wordpress/element'; + +function useTransformState( { url, naturalWidth, naturalHeight } ) { + const [ editedUrl, setEditedUrl ] = useState(); + const [ crop, setCrop ] = useState(); + const [ position, setPosition ] = useState( { x: 0, y: 0 } ); + const [ zoom, setZoom ] = useState(); + const [ rotation, setRotation ] = useState(); + const [ aspect, setAspect ] = useState(); + const [ defaultAspect, setDefaultAspect ] = useState(); + + const initializeTransformValues = useCallback( () => { + setPosition( { x: 0, y: 0 } ); + setZoom( 100 ); + setRotation( 0 ); + setAspect( naturalWidth / naturalHeight ); + setDefaultAspect( naturalWidth / naturalHeight ); + }, [ + naturalWidth, + naturalHeight, + setPosition, + setZoom, + setRotation, + setAspect, + setDefaultAspect, + ] ); + + const rotateClockwise = useCallback( () => { + const angle = ( rotation + 90 ) % 360; + + let naturalAspectRatio = naturalWidth / naturalHeight; + + if ( rotation % 180 === 90 ) { + naturalAspectRatio = naturalHeight / naturalWidth; + } + + if ( angle === 0 ) { + setEditedUrl(); + setRotation( angle ); + setAspect( 1 / aspect ); + setPosition( { + x: -( position.y * naturalAspectRatio ), + y: position.x * naturalAspectRatio, + } ); + return; + } + + function editImage( event ) { + const canvas = document.createElement( 'canvas' ); + + let translateX = 0; + let translateY = 0; + + if ( angle % 180 ) { + canvas.width = event.target.height; + canvas.height = event.target.width; + } else { + canvas.width = event.target.width; + canvas.height = event.target.height; + } + + if ( angle === 90 || angle === 180 ) { + translateX = canvas.width; + } + + if ( angle === 270 || angle === 180 ) { + translateY = canvas.height; + } + + const context = canvas.getContext( '2d' ); + + context.translate( translateX, translateY ); + context.rotate( ( angle * Math.PI ) / 180 ); + context.drawImage( event.target, 0, 0 ); + + canvas.toBlob( ( blob ) => { + setEditedUrl( URL.createObjectURL( blob ) ); + setRotation( angle ); + setAspect( 1 / aspect ); + setPosition( { + x: -( position.y * naturalAspectRatio ), + y: position.x * naturalAspectRatio, + } ); + } ); + } + + const el = new window.Image(); + el.src = url; + el.onload = editImage; + }, [ + rotation, + naturalWidth, + naturalHeight, + setEditedUrl, + setRotation, + setAspect, + setPosition, + ] ); + + return useMemo( + () => ( { + editedUrl, + setEditedUrl, + crop, + setCrop, + position, + setPosition, + zoom, + setZoom, + rotation, + setRotation, + rotateClockwise, + aspect, + setAspect, + defaultAspect, + initializeTransformValues, + } ), + [ + editedUrl, + setEditedUrl, + crop, + setCrop, + position, + setPosition, + zoom, + setZoom, + rotation, + setRotation, + rotateClockwise, + aspect, + setAspect, + defaultAspect, + initializeTransformValues, + ] + ); +} + +export default function useTransformImage( imageProperties, isEditing ) { + const transformState = useTransformState( imageProperties ); + const { initializeTransformValues } = transformState; + + useEffect( () => { + if ( isEditing ) { + initializeTransformValues(); + } + }, [ isEditing, initializeTransformValues ] ); + + return transformState; +} diff --git a/packages/block-library/src/image/image-editing/zoom-dropdown.js b/packages/block-library/src/image/image-editing/zoom-dropdown.js new file mode 100644 index 00000000000000..2694382e3860c2 --- /dev/null +++ b/packages/block-library/src/image/image-editing/zoom-dropdown.js @@ -0,0 +1,40 @@ +/** + * WordPress dependencies + */ +import { ToolbarButton, RangeControl, Dropdown } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { search } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import { MIN_ZOOM, MAX_ZOOM, POPOVER_PROPS } from './constants'; +import { useImageEditingContext } from './context'; + +export default function ZoomDropdown() { + const { isInProgress, zoom, setZoom } = useImageEditingContext(); + return ( + <Dropdown + contentClassName="wp-block-image__zoom" + popoverProps={ POPOVER_PROPS } + renderToggle={ ( { isOpen, onToggle } ) => ( + <ToolbarButton + icon={ search } + label={ __( 'Zoom' ) } + onClick={ onToggle } + aria-expanded={ isOpen } + disabled={ isInProgress } + /> + ) } + renderContent={ () => ( + <RangeControl + label={ __( 'Zoom' ) } + min={ MIN_ZOOM } + max={ MAX_ZOOM } + value={ Math.round( zoom ) } + onChange={ setZoom } + /> + ) } + /> + ); +} diff --git a/packages/block-library/src/image/image-editor.js b/packages/block-library/src/image/image-editor.js deleted file mode 100644 index a3401132390fb3..00000000000000 --- a/packages/block-library/src/image/image-editor.js +++ /dev/null @@ -1,376 +0,0 @@ -/** - * External dependencies - */ - -import Cropper from 'react-easy-crop'; -import classnames from 'classnames'; - -/** - * WordPress dependencies - */ - -import { BlockControls } from '@wordpress/block-editor'; -import { useState } from '@wordpress/element'; -import { - search, - check, - rotateRight as rotateRightIcon, - aspectRatio as aspectRatioIcon, -} from '@wordpress/icons'; -import { - ToolbarGroup, - ToolbarButton, - ToolbarItem, - Spinner, - RangeControl, - DropdownMenu, - MenuGroup, - MenuItem, - Dropdown, -} from '@wordpress/components'; -import { __, sprintf } from '@wordpress/i18n'; -import { useDispatch } from '@wordpress/data'; -import apiFetch from '@wordpress/api-fetch'; - -const MIN_ZOOM = 100; -const MAX_ZOOM = 300; -const POPOVER_PROPS = { - position: 'bottom right', - isAlternate: true, -}; - -function AspectGroup( { aspectRatios, isDisabled, label, onClick, value } ) { - return ( - <MenuGroup label={ label }> - { aspectRatios.map( ( { title, aspect } ) => ( - <MenuItem - key={ aspect } - disabled={ isDisabled } - onClick={ () => { - onClick( aspect ); - } } - role="menuitemradio" - isSelected={ aspect === value } - icon={ aspect === value ? check : undefined } - > - { title } - </MenuItem> - ) ) } - </MenuGroup> - ); -} - -function AspectMenu( { - toggleProps, - isDisabled, - onClick, - value, - defaultValue, -} ) { - return ( - <DropdownMenu - icon={ aspectRatioIcon } - label={ __( 'Aspect Ratio' ) } - popoverProps={ POPOVER_PROPS } - toggleProps={ toggleProps } - className="wp-block-image__aspect-ratio" - > - { ( { onClose } ) => ( - <> - <AspectGroup - isDisabled={ isDisabled } - onClick={ ( aspect ) => { - onClick( aspect ); - onClose(); - } } - value={ value } - aspectRatios={ [ - { - title: __( 'Original' ), - aspect: defaultValue, - }, - { - title: __( 'Square' ), - aspect: 1, - }, - ] } - /> - <AspectGroup - label={ __( 'Landscape' ) } - isDisabled={ isDisabled } - onClick={ ( aspect ) => { - onClick( aspect ); - onClose(); - } } - value={ value } - aspectRatios={ [ - { - title: __( '16:10' ), - aspect: 16 / 10, - }, - { - title: __( '16:9' ), - aspect: 16 / 9, - }, - { - title: __( '4:3' ), - aspect: 4 / 3, - }, - { - title: __( '3:2' ), - aspect: 3 / 2, - }, - ] } - /> - <AspectGroup - label={ __( 'Portrait' ) } - isDisabled={ isDisabled } - onClick={ ( aspect ) => { - onClick( aspect ); - onClose(); - } } - value={ value } - aspectRatios={ [ - { - title: __( '10:16' ), - aspect: 10 / 16, - }, - { - title: __( '9:16' ), - aspect: 9 / 16, - }, - { - title: __( '3:4' ), - aspect: 3 / 4, - }, - { - title: __( '2:3' ), - aspect: 2 / 3, - }, - ] } - /> - </> - ) } - </DropdownMenu> - ); -} - -export default function ImageEditor( { - id, - url, - setAttributes, - naturalWidth, - naturalHeight, - width, - height, - clientWidth, - setIsEditingImage, -} ) { - const { createErrorNotice } = useDispatch( 'core/notices' ); - const [ inProgress, setIsProgress ] = useState( false ); - const [ crop, setCrop ] = useState( null ); - const [ position, setPosition ] = useState( { x: 0, y: 0 } ); - const [ zoom, setZoom ] = useState( 100 ); - const [ aspect, setAspect ] = useState( naturalWidth / naturalHeight ); - const [ rotation, setRotation ] = useState( 0 ); - const [ editedUrl, setEditedUrl ] = useState(); - - const editedWidth = width; - let editedHeight = height || ( clientWidth * naturalHeight ) / naturalWidth; - let naturalAspectRatio = naturalWidth / naturalHeight; - - if ( rotation % 180 === 90 ) { - editedHeight = ( clientWidth * naturalWidth ) / naturalHeight; - naturalAspectRatio = naturalHeight / naturalWidth; - } - - function apply() { - setIsProgress( true ); - - let attrs = {}; - - // The crop script may return some very small, sub-pixel values when the image was not cropped. - // Crop only when the new size has changed by more than 0.1%. - if ( crop.width < 99.9 || crop.height < 99.9 ) { - attrs = crop; - } - - if ( rotation > 0 ) { - attrs.rotation = rotation; - } - - attrs.src = url; - - apiFetch( { - path: `/wp/v2/media/${ id }/edit`, - method: 'POST', - data: attrs, - } ) - .then( ( response ) => { - setAttributes( { - id: response.id, - url: response.source_url, - height: height && width ? width / aspect : undefined, - } ); - } ) - .catch( ( error ) => { - createErrorNotice( - sprintf( - /* translators: 1. Error message */ - __( 'Could not edit image. %s' ), - error.message - ), - { - id: 'image-editing-error', - type: 'snackbar', - } - ); - } ) - .finally( () => { - setIsProgress( false ); - setIsEditingImage( false ); - } ); - } - - function rotate() { - const angle = ( rotation + 90 ) % 360; - - if ( angle === 0 ) { - setEditedUrl(); - setRotation( angle ); - setAspect( 1 / aspect ); - setPosition( { - x: -( position.y * naturalAspectRatio ), - y: position.x * naturalAspectRatio, - } ); - return; - } - - function editImage( event ) { - const canvas = document.createElement( 'canvas' ); - - let translateX = 0; - let translateY = 0; - - if ( angle % 180 ) { - canvas.width = event.target.height; - canvas.height = event.target.width; - } else { - canvas.width = event.target.width; - canvas.height = event.target.height; - } - - if ( angle === 90 || angle === 180 ) { - translateX = canvas.width; - } - - if ( angle === 270 || angle === 180 ) { - translateY = canvas.height; - } - - const context = canvas.getContext( '2d' ); - - context.translate( translateX, translateY ); - context.rotate( ( angle * Math.PI ) / 180 ); - context.drawImage( event.target, 0, 0 ); - - canvas.toBlob( ( blob ) => { - setEditedUrl( URL.createObjectURL( blob ) ); - setRotation( angle ); - setAspect( 1 / aspect ); - setPosition( { - x: -( position.y * naturalAspectRatio ), - y: position.x * naturalAspectRatio, - } ); - } ); - } - - const el = new window.Image(); - el.src = url; - el.onload = editImage; - } - - return ( - <> - <div - className={ classnames( 'wp-block-image__crop-area', { - 'is-applying': inProgress, - } ) } - style={ { - width: editedWidth, - height: editedHeight, - } } - > - <Cropper - image={ editedUrl || url } - disabled={ inProgress } - minZoom={ MIN_ZOOM / 100 } - maxZoom={ MAX_ZOOM / 100 } - crop={ position } - zoom={ zoom / 100 } - aspect={ aspect } - onCropChange={ setPosition } - onCropComplete={ ( newCropPercent ) => { - setCrop( newCropPercent ); - } } - onZoomChange={ ( newZoom ) => { - setZoom( newZoom * 100 ); - } } - /> - { inProgress && <Spinner /> } - </div> - <BlockControls> - <ToolbarGroup> - <Dropdown - contentClassName="wp-block-image__zoom" - popoverProps={ POPOVER_PROPS } - renderToggle={ ( { isOpen, onToggle } ) => ( - <ToolbarButton - icon={ search } - label={ __( 'Zoom' ) } - onClick={ onToggle } - aria-expanded={ isOpen } - disabled={ inProgress } - /> - ) } - renderContent={ () => ( - <RangeControl - min={ MIN_ZOOM } - max={ MAX_ZOOM } - value={ Math.round( zoom ) } - onChange={ setZoom } - /> - ) } - /> - <ToolbarItem> - { ( toggleProps ) => ( - <AspectMenu - toggleProps={ toggleProps } - isDisabled={ inProgress } - onClick={ setAspect } - value={ aspect } - defaultValue={ naturalWidth / naturalHeight } - /> - ) } - </ToolbarItem> - </ToolbarGroup> - <ToolbarGroup> - <ToolbarButton - icon={ rotateRightIcon } - label={ __( 'Rotate' ) } - onClick={ rotate } - disabled={ inProgress } - /> - </ToolbarGroup> - <ToolbarGroup> - <ToolbarButton onClick={ apply } disabled={ inProgress }> - { __( 'Apply' ) } - </ToolbarButton> - <ToolbarButton onClick={ () => setIsEditingImage( false ) }> - { __( 'Cancel' ) } - </ToolbarButton> - </ToolbarGroup> - </BlockControls> - </> - ); -} diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index 3e32afd372524d..27891132e9bca3 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -33,13 +33,14 @@ import { __, sprintf } from '@wordpress/i18n'; import { getPath } from '@wordpress/url'; import { createBlock } from '@wordpress/blocks'; import { crop, upload } from '@wordpress/icons'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies */ import { createUpgradedEmbedBlock } from '../embed/util'; import useClientWidth from './use-client-width'; -import ImageEditor from './image-editor'; +import ImageEditor, { ImageEditingProvider } from './image-editing'; import { isExternalImage } from './edit'; /** @@ -82,10 +83,22 @@ export default function Image( { } ) { const captionRef = useRef(); const prevUrl = usePrevious( url ); - const image = useSelect( + const { image, multiImageSelection } = useSelect( ( select ) => { const { getMedia } = select( 'core' ); - return id && isSelected ? getMedia( id ) : null; + const { getMultiSelectedBlockClientIds, getBlockName } = select( + 'core/block-editor' + ); + const multiSelectedClientIds = getMultiSelectedBlockClientIds(); + return { + image: id && isSelected ? getMedia( id ) : null, + multiImageSelection: + multiSelectedClientIds.length && + multiSelectedClientIds.every( + ( clientId ) => + getBlockName( clientId ) === 'core/image' + ), + }; }, [ id, isSelected ] ); @@ -107,7 +120,7 @@ export default function Image( { } ); const { toggleSelection } = useDispatch( 'core/block-editor' ); const { createErrorNotice, createSuccessNotice } = useDispatch( - 'core/notices' + noticesStore ); const isLargeViewport = useViewportMatch( 'medium' ); const [ captionFocused, setCaptionFocused ] = useState( false ); @@ -244,11 +257,12 @@ export default function Image( { }, [ isSelected ] ); const canEditImage = id && naturalWidth && naturalHeight && imageEditing; + const allowCrop = ! multiImageSelection && canEditImage && ! isEditingImage; const controls = ( <> <BlockControls> - { ! isEditingImage && ( + { ! multiImageSelection && ! isEditingImage && ( <ToolbarGroup> <ImageURLInputUI url={ href || '' } @@ -262,7 +276,7 @@ export default function Image( { /> </ToolbarGroup> ) } - { canEditImage && ! isEditingImage && ( + { allowCrop && ( <ToolbarGroup> <ToolbarButton onClick={ () => setIsEditingImage( true ) } @@ -280,7 +294,7 @@ export default function Image( { /> </ToolbarGroup> ) } - { ! isEditingImage && ( + { ! multiImageSelection && ! isEditingImage && ( <MediaReplaceFlow mediaId={ id } mediaURL={ url } @@ -294,23 +308,25 @@ export default function Image( { </BlockControls> <InspectorControls> <PanelBody title={ __( 'Image settings' ) }> - <TextareaControl - label={ __( 'Alt text (alternative text)' ) } - value={ alt } - onChange={ updateAlt } - help={ - <> - <ExternalLink href="https://www.w3.org/WAI/tutorials/images/decision-tree"> + { ! multiImageSelection && ( + <TextareaControl + label={ __( 'Alt text (alternative text)' ) } + value={ alt } + onChange={ updateAlt } + help={ + <> + <ExternalLink href="https://www.w3.org/WAI/tutorials/images/decision-tree"> + { __( + 'Describe the purpose of the image' + ) } + </ExternalLink> { __( - 'Describe the purpose of the image' + 'Leave empty if the image is purely decorative.' ) } - </ExternalLink> - { __( - 'Leave empty if the image is purely decorative.' - ) } - </> - } - /> + </> + } + /> + ) } <ImageSizeControl onChangeImage={ updateImage } onChange={ ( value ) => setAttributes( value ) } @@ -400,15 +416,12 @@ export default function Image( { if ( canEditImage && isEditingImage ) { img = ( <ImageEditor - id={ id } url={ url } - setAttributes={ setAttributes } - naturalWidth={ naturalWidth } - naturalHeight={ naturalHeight } width={ width } height={ height } clientWidth={ clientWidth } - setIsEditingImage={ setIsEditingImage } + naturalHeight={ naturalHeight } + naturalWidth={ naturalWidth } /> ); } else if ( ! isResizable || ! imageWidthWithinContainer ) { @@ -493,13 +506,25 @@ export default function Image( { } return ( - <> + <ImageEditingProvider + id={ id } + url={ url } + naturalWidth={ naturalWidth } + naturalHeight={ naturalHeight } + clientWidth={ clientWidth } + onSaveImage={ ( imageAttributes ) => + setAttributes( imageAttributes ) + } + isEditing={ isEditingImage } + onFinishEditing={ () => setIsEditingImage( false ) } + > { controls } { img } { ( ! RichText.isEmpty( caption ) || isSelected ) && ( <RichText ref={ captionRef } tagName="figcaption" + aria-label={ __( 'Image caption text' ) } placeholder={ __( 'Write caption…' ) } value={ caption } unstableOnFocus={ onFocusCaption } @@ -513,6 +538,6 @@ export default function Image( { } /> ) } - </> + </ImageEditingProvider> ); } diff --git a/packages/block-library/src/image/style.scss b/packages/block-library/src/image/style.scss index a20c12dc9046b1..3771670675ed8f 100644 --- a/packages/block-library/src/image/style.scss +++ b/packages/block-library/src/image/style.scss @@ -58,32 +58,32 @@ figcaption { @include caption-style(); } -} -// Variations -.is-style-rounded img { - // We use an absolute pixel to prevent the oval shape that a value of 50% would give - // to rectangular images. A pill-shape is better than otherwise. - border-radius: 9999px; -} + // Variations + &.is-style-rounded img { + // We use an absolute pixel to prevent the oval shape that a value of 50% would give + // to rectangular images. A pill-shape is better than otherwise. + border-radius: 9999px; + } -// The following variation is deprecated. -// The CSS is kept here for the time being, to support blocks using the old variation. -.is-style-circle-mask img { - // We use an absolute pixel to prevent the oval shape that a value of 50% would give - // to rectangular images. A pill-shape is better than otherwise. - border-radius: 9999px; + // The following variation is deprecated. + // The CSS is kept here for the time being, to support blocks using the old variation. + &.is-style-circle-mask img { + // We use an absolute pixel to prevent the oval shape that a value of 50% would give + // to rectangular images. A pill-shape is better than otherwise. + border-radius: 9999px; - // If a browser supports it, we will switch to using a circular SVG mask. - // The stylelint override is necessary to use the SVG inline here. - @supports (mask-image: none) or (-webkit-mask-image: none) { - /* stylelint-disable */ - mask-image: url('data:image/svg+xml;utf8,<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="50"/></svg>'); - /* stylelint-enable */ - mask-mode: alpha; - mask-repeat: no-repeat; - mask-size: contain; - mask-position: center; - border-radius: 0; + // If a browser supports it, we will switch to using a circular SVG mask. + // The stylelint override is necessary to use the SVG inline here. + @supports (mask-image: none) or (-webkit-mask-image: none) { + /* stylelint-disable */ + mask-image: url('data:image/svg+xml;utf8,<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="50"/></svg>'); + /* stylelint-enable */ + mask-mode: alpha; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + border-radius: 0; + } } } diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 45d04f2a76c8b5..7cfcb0e0c74a9f 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -2,9 +2,7 @@ * WordPress dependencies */ import '@wordpress/core-data'; -import '@wordpress/notices'; import '@wordpress/block-editor'; -import '@wordpress/reusable-blocks'; import { registerBlockType, setDefaultBlockName, @@ -59,7 +57,7 @@ import * as textColumns from './text-columns'; import * as verse from './verse'; import * as video from './video'; import * as tagCloud from './tag-cloud'; -import * as classic from './classic'; +import * as classic from './freeform'; import * as socialLinks from './social-links'; import * as socialLink from './social-link'; @@ -192,8 +190,7 @@ export const registerCoreBlocks = ( /** * Function to register experimental core blocks depending on editor settings. * - * @param {Object} settings Editor settings. - * + * @param {boolean} enableFSEBlocks Whether to enable the full site editing blocks. * @example * ```js * import { __experimentalRegisterExperimentalCoreBlocks } from '@wordpress/block-library'; @@ -203,15 +200,13 @@ export const registerCoreBlocks = ( */ export const __experimentalRegisterExperimentalCoreBlocks = process.env.GUTENBERG_PHASE === 2 - ? ( settings ) => { - const { __experimentalEnableFullSiteEditing } = settings; - + ? ( enableFSEBlocks ) => { [ navigation, navigationLink, // Register Full Site Editing Blocks. - ...( __experimentalEnableFullSiteEditing + ...( enableFSEBlocks ? [ siteLogo, siteTagline, diff --git a/packages/block-library/src/index.native.js b/packages/block-library/src/index.native.js index 4b86e2fbe51282..65785c7f3fb378 100644 --- a/packages/block-library/src/index.native.js +++ b/packages/block-library/src/index.native.js @@ -58,7 +58,7 @@ import * as textColumns from './text-columns'; import * as verse from './verse'; import * as video from './video'; import * as tagCloud from './tag-cloud'; -import * as classic from './classic'; +import * as classic from './freeform'; import * as group from './group'; import * as buttons from './buttons'; import * as socialLink from './social-link'; @@ -223,6 +223,7 @@ export const registerCoreBlocks = () => { socialLink, socialLinks, pullquote, + file, ].forEach( registerBlock ); registerBlockVariations( socialLink ); diff --git a/packages/block-library/src/latest-comments/block.json b/packages/block-library/src/latest-comments/block.json index 8ad73394cd93bb..37600026f8f710 100644 --- a/packages/block-library/src/latest-comments/block.json +++ b/packages/block-library/src/latest-comments/block.json @@ -25,5 +25,7 @@ "supports": { "align": true, "html": false - } + }, + "editorStyle": "wp-block-latest-comments-editor", + "style": "wp-block-latest-comments" } diff --git a/packages/block-library/src/latest-comments/index.php b/packages/block-library/src/latest-comments/index.php index 555a125c4aa2ba..6343a46d0be91e 100644 --- a/packages/block-library/src/latest-comments/index.php +++ b/packages/block-library/src/latest-comments/index.php @@ -41,8 +41,8 @@ function wp_latest_comments_draft_or_post_title( $post = 0 ) { * @return string Returns the post content with latest comments added. */ function render_block_core_latest_comments( $attributes = array() ) { - // This filter is documented in wp-includes/widgets/class-wp-widget-recent-comments.php. $comments = get_comments( + // This filter is documented in wp-includes/widgets/class-wp-widget-recent-comments.php. apply_filters( 'widget_comments_args', array( diff --git a/packages/block-library/src/latest-posts/block.json b/packages/block-library/src/latest-posts/block.json index fbe711ee99788c..2f603bc57274e4 100644 --- a/packages/block-library/src/latest-posts/block.json +++ b/packages/block-library/src/latest-posts/block.json @@ -84,5 +84,7 @@ "supports": { "align": true, "html": false - } + }, + "editorStyle": "wp-block-latest-posts-editor", + "style": "wp-block-latest-posts" } diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index 41e79e4e8ba544..d24e69abf3f9ee 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -510,11 +510,7 @@ export default function LatestPostsEdit( { attributes, setAttributes } ) { .join( ' ' ) } { /* translators: excerpt truncation character, default … */ } { __( ' … ' ) } - <a - href={ post.link } - target="_blank" - rel="noopener noreferrer" - > + <a href={ post.link } rel="noopener noreferrer"> { __( 'Read more' ) } </a> </> @@ -529,7 +525,6 @@ export default function LatestPostsEdit( { attributes, setAttributes } ) { { addLinkToFeaturedImage ? ( <a href={ post.link } - target="_blank" rel="noreferrer noopener" > { featuredImage } @@ -539,11 +534,7 @@ export default function LatestPostsEdit( { attributes, setAttributes } ) { ) } </div> ) } - <a - href={ post.link } - target="_blank" - rel="noreferrer noopener" - > + <a href={ post.link } rel="noreferrer noopener"> { titleTrimmed ? ( <RawHTML>{ titleTrimmed }</RawHTML> ) : ( diff --git a/packages/block-library/src/latest-posts/editor.scss b/packages/block-library/src/latest-posts/editor.scss index 431a0794704b4b..51be592be732b5 100644 --- a/packages/block-library/src/latest-posts/editor.scss +++ b/packages/block-library/src/latest-posts/editor.scss @@ -1,4 +1,4 @@ -.block-editor .wp-block-latest-posts { +.wp-block-latest-posts { padding-left: 2.5em; &.is-grid { padding-left: 0; diff --git a/packages/block-library/src/list/block.json b/packages/block-library/src/list/block.json index 848c017842d392..002f0bc82bc7bb 100644 --- a/packages/block-library/src/list/block.json +++ b/packages/block-library/src/list/block.json @@ -28,9 +28,12 @@ "supports": { "anchor": true, "className": false, + "fontSize": true, "color": { "gradients": true }, "__unstablePasteTextInline": true - } + }, + "editorStyle": "wp-block-list-editor", + "style": "wp-block-list" } diff --git a/packages/block-library/src/list/edit.js b/packages/block-library/src/list/edit.js index 5ca9052dbf6ecb..867c0b1e03c272 100644 --- a/packages/block-library/src/list/edit.js +++ b/packages/block-library/src/list/edit.js @@ -167,6 +167,7 @@ export default function ListEdit( { setAttributes( { values: nextValues } ) } value={ values } + aria-label={ __( 'List text' ) } placeholder={ __( 'Write list…' ) } onMerge={ mergeBlocks } onSplit={ ( value ) => diff --git a/packages/block-library/src/media-text/block.json b/packages/block-library/src/media-text/block.json index e88b1b1d1b8182..0e175cf04bd538 100644 --- a/packages/block-library/src/media-text/block.json +++ b/packages/block-library/src/media-text/block.json @@ -89,5 +89,7 @@ "gradients": true, "link": true } - } + }, + "editorStyle": "wp-block-media-text-editor", + "style": "wp-block-media-text" } diff --git a/packages/block-library/src/media-text/edit.js b/packages/block-library/src/media-text/edit.js index a837b246fd2352..853b1585c4ee1a 100644 --- a/packages/block-library/src/media-text/edit.js +++ b/packages/block-library/src/media-text/edit.js @@ -242,7 +242,7 @@ function MediaTextEdit( { attributes, isSelected, setAttributes } ) { } /> ) } - { imageFill && ( + { imageFill && mediaUrl && mediaType === 'image' && ( <FocalPointPicker label={ __( 'Focal point picker' ) } url={ mediaUrl } @@ -286,13 +286,8 @@ function MediaTextEdit( { attributes, isSelected, setAttributes } ) { } ); const innerBlocksProps = useInnerBlocksProps( - { - className: 'wp-block-media-text__content', - }, - { - template: TEMPLATE, - templateInsertUpdatesSelection: false, - } + { className: 'wp-block-media-text__content' }, + { template: TEMPLATE } ); return ( diff --git a/packages/block-library/src/media-text/edit.native.js b/packages/block-library/src/media-text/edit.native.js index 3eaab4e09988fb..f6ad9cf9fdbc43 100644 --- a/packages/block-library/src/media-text/edit.native.js +++ b/packages/block-library/src/media-text/edit.native.js @@ -379,10 +379,7 @@ class MediaTextEdit extends Component { ...innerBlockContainerStyle, } } > - <InnerBlocks - template={ TEMPLATE } - templateInsertUpdatesSelection={ false } - /> + <InnerBlocks template={ TEMPLATE } /> </View> </View> </> diff --git a/packages/block-library/src/media-text/style.scss b/packages/block-library/src/media-text/style.scss index 93b6d2a0c803b8..98c7e0629130f1 100644 --- a/packages/block-library/src/media-text/style.scss +++ b/packages/block-library/src/media-text/style.scss @@ -72,13 +72,18 @@ vertical-align: middle; } -.wp-block-media-text.is-image-fill figure.wp-block-media-text__media { +.wp-block-media-text.is-image-fill .wp-block-media-text__media { height: 100%; min-height: 250px; background-size: cover; } -.wp-block-media-text.is-image-fill figure.wp-block-media-text__media > img { +.wp-block-media-text.is-image-fill .wp-block-media-text__media > a { + display: block; + height: 100%; +} + +.wp-block-media-text.is-image-fill .wp-block-media-text__media img { // The image is visually hidden but accessible to assistive technologies. position: absolute; width: 1px; diff --git a/packages/block-library/src/more/block.json b/packages/block-library/src/more/block.json index 4279443517b000..f49bbd58583a34 100644 --- a/packages/block-library/src/more/block.json +++ b/packages/block-library/src/more/block.json @@ -16,5 +16,6 @@ "className": false, "html": false, "multiple": false - } + }, + "editorStyle": "wp-block-more-editor" } diff --git a/packages/block-library/src/more/editor.scss b/packages/block-library/src/more/editor.scss index 0d522b7cc2c50e..4aa6b141d9aba0 100644 --- a/packages/block-library/src/more/editor.scss +++ b/packages/block-library/src/more/editor.scss @@ -5,7 +5,7 @@ margin-bottom: $default-block-margin; } -.block-editor .wp-block-more { // needs specificity +.wp-block-more { display: block; text-align: center; white-space: nowrap; diff --git a/packages/block-library/src/navigation-link/block.json b/packages/block-library/src/navigation-link/block.json index 689707fc5ba77e..2104f50a13870e 100644 --- a/packages/block-library/src/navigation-link/block.json +++ b/packages/block-library/src/navigation-link/block.json @@ -42,5 +42,7 @@ "supports": { "reusable": false, "html": false - } + }, + "editorStyle": "wp-block-navigation-link-editor", + "style": "wp-block-navigation-link" } diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 3791d7e103cdc4..4f438723393990 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -64,6 +64,8 @@ const useIsDraggingWithin = ( elementRef ) => { const [ isDraggingWithin, setIsDraggingWithin ] = useState( false ); useEffect( () => { + const { ownerDocument } = elementRef.current; + function handleDragStart( event ) { // Check the first time when the dragging starts. handleDragEnter( event ); @@ -84,15 +86,16 @@ const useIsDraggingWithin = ( elementRef ) => { } // Bind these events to the document to catch all drag events. - // Ideally, we can also use `event.relatedTarget`, but sadly that doesn't work in Safari. - document.addEventListener( 'dragstart', handleDragStart ); - document.addEventListener( 'dragend', handleDragEnd ); - document.addEventListener( 'dragenter', handleDragEnter ); + // Ideally, we can also use `event.relatedTarget`, but sadly that + // doesn't work in Safari. + ownerDocument.addEventListener( 'dragstart', handleDragStart ); + ownerDocument.addEventListener( 'dragend', handleDragEnd ); + ownerDocument.addEventListener( 'dragenter', handleDragEnter ); return () => { - document.removeEventListener( 'dragstart', handleDragStart ); - document.removeEventListener( 'dragend', handleDragEnd ); - document.removeEventListener( 'dragenter', handleDragEnter ); + ownerDocument.removeEventListener( 'dragstart', handleDragStart ); + ownerDocument.removeEventListener( 'dragend', handleDragEnd ); + ownerDocument.removeEventListener( 'dragenter', handleDragEnter ); }; }, [] ); @@ -167,7 +170,7 @@ function NavigationLinkEdit( { // Show the LinkControl on mount if the URL is empty // ( When adding a new menu item) - // This can't be done in the useState call because it cconflicts + // This can't be done in the useState call because it conflicts // with the autofocus behavior of the BlockListBlock component. useEffect( () => { if ( ! url ) { @@ -359,6 +362,7 @@ function NavigationLinkEdit( { createBlock( 'core/navigation-link' ) ) } + aria-label={ __( 'Navigation link text' ) } placeholder={ itemLabelPlaceholder } keepPlaceholderOnFocus withoutInteractiveFormatting diff --git a/packages/block-library/src/navigation-link/style.scss b/packages/block-library/src/navigation-link/style.scss index 7665d5d3b76608..34558950e3391c 100644 --- a/packages/block-library/src/navigation-link/style.scss +++ b/packages/block-library/src/navigation-link/style.scss @@ -103,10 +103,36 @@ } } +// Force links to inherit text decoration applied to navigation block. +.wp-block-navigation[style*="text-decoration"] { + .wp-block-navigation__container, + .wp-block-navigation-link { + text-decoration: inherit; + } + .wp-block-navigation-link__content { + text-decoration: inherit; + + &:focus, + &:active { + text-decoration: inherit; + } + } +} + +.wp-block-navigation:not([style*="text-decoration"]) { + .wp-block-navigation-link__content { + text-decoration: none; + + &:focus, + &:active { + text-decoration: none; + } + } +} + // All links .wp-block-navigation-link__content { color: inherit; - text-decoration: none; padding: 0.5em 1em; + .wp-block-navigation-link__content { diff --git a/packages/block-library/src/navigation/block.json b/packages/block-library/src/navigation/block.json index b22fdfad89a77a..5b70d51eaafa7e 100644 --- a/packages/block-library/src/navigation/block.json +++ b/packages/block-library/src/navigation/block.json @@ -50,9 +50,13 @@ "html": false, "inserter": true, "fontSize": true, - "color": { - "textColor": true, - "backgroundColor": true - } - } + "__experimentalFontStyle": true, + "__experimentalFontWeight": true, + "__experimentalTextTransform": true, + "color": true, + "__experimentalFontFamily": true, + "__experimentalTextDecoration": true + }, + "editorStyle": "wp-block-navigation-editor", + "style": "wp-block-navigation" } diff --git a/packages/block-library/src/navigation/deprecated.js b/packages/block-library/src/navigation/deprecated.js index 9f8b8bce707096..0ee64c0a44da89 100644 --- a/packages/block-library/src/navigation/deprecated.js +++ b/packages/block-library/src/navigation/deprecated.js @@ -1,14 +1,113 @@ /** * External dependencies */ -import { omit } from 'lodash'; +import { mapValues, omit } from 'lodash'; /** * WordPress dependencies */ import { InnerBlocks } from '@wordpress/block-editor'; +const TYPOGRAPHY_PRESET_DEPRECATION_MAP = { + fontStyle: 'var:preset|font-style|', + fontWeight: 'var:preset|font-weight|', + textDecoration: 'var:preset|text-decoration|', + textTransform: 'var:preset|text-transform|', +}; + export default [ + { + attributes: { + orientation: { + type: 'string', + }, + textColor: { + type: 'string', + }, + customTextColor: { + type: 'string', + }, + rgbTextColor: { + type: 'string', + }, + backgroundColor: { + type: 'string', + }, + customBackgroundColor: { + type: 'string', + }, + rgbBackgroundColor: { + type: 'string', + }, + itemsJustification: { + type: 'string', + }, + showSubmenuIcon: { + type: 'boolean', + default: true, + }, + }, + supports: { + align: [ 'wide', 'full' ], + anchor: true, + html: false, + inserter: true, + fontSize: true, + __experimentalFontStyle: true, + __experimentalFontWeight: true, + __experimentalTextTransform: true, + color: true, + __experimentalFontFamily: true, + __experimentalTextDecoration: true, + }, + save() { + return <InnerBlocks.Content />; + }, + isEligible( attributes ) { + if ( ! attributes.style || ! attributes.style.typography ) { + return false; + } + for ( const styleAttribute in TYPOGRAPHY_PRESET_DEPRECATION_MAP ) { + const attributeValue = + attributes.style.typography[ styleAttribute ]; + if ( + attributeValue && + attributeValue.startsWith( + TYPOGRAPHY_PRESET_DEPRECATION_MAP[ styleAttribute ] + ) + ) { + return true; + } + } + return false; + }, + migrate( attributes ) { + return { + ...attributes, + style: { + ...attributes.style, + typography: mapValues( + attributes.style.typography, + ( value, key ) => { + const prefix = + TYPOGRAPHY_PRESET_DEPRECATION_MAP[ key ]; + if ( prefix && value.startsWith( prefix ) ) { + const newValue = value.slice( prefix.length ); + if ( + 'textDecoration' === key && + 'strikethrough' === newValue + ) { + return 'line-through'; + } + return newValue; + } + return value; + } + ), + }, + }; + }, + }, { attributes: { className: { diff --git a/packages/block-library/src/navigation/edit.js b/packages/block-library/src/navigation/edit.js index 6822883685f101..cd47d5b89a0b9a 100644 --- a/packages/block-library/src/navigation/edit.js +++ b/packages/block-library/src/navigation/edit.js @@ -47,7 +47,13 @@ function Navigation( { const { selectBlock } = useDispatch( 'core/block-editor' ); - const blockProps = useBlockProps(); + const blockProps = useBlockProps( { + className: classnames( className, { + [ `items-justified-${ attributes.itemsJustification }` ]: attributes.itemsJustification, + 'is-vertical': attributes.orientation === 'vertical', + } ), + } ); + const { navigatorToolbarButton, navigatorModal } = useBlockNavigator( clientId ); @@ -69,7 +75,6 @@ function Navigation( { isSelected ? InnerBlocks.DefaultAppender : false, - templateInsertUpdatesSelection: false, __experimentalAppenderTagName: 'li', __experimentalCaptureToolbars: true, // Template lock set to false here so that the Nav @@ -105,11 +110,6 @@ function Navigation( { }; } - const blockClassNames = classnames( className, { - [ `items-justified-${ attributes.itemsJustification }` ]: attributes.itemsJustification, - 'is-vertical': attributes.orientation === 'vertical', - } ); - return ( <> <BlockControls> @@ -171,13 +171,7 @@ function Navigation( { </PanelBody> ) } </InspectorControls> - <nav - { ...blockProps } - className={ classnames( - blockProps.className, - blockClassNames - ) } - > + <nav { ...blockProps }> <ul { ...innerBlocksProps } /> </nav> </> @@ -214,7 +208,8 @@ export default compose( [ } dispatch( 'core/block-editor' ).replaceInnerBlocks( clientId, - blocks + blocks, + true ); }, }; diff --git a/packages/block-library/src/navigation/editor.scss b/packages/block-library/src/navigation/editor.scss index 9c605bbb796071..cc439d16f0325b 100644 --- a/packages/block-library/src/navigation/editor.scss +++ b/packages/block-library/src/navigation/editor.scss @@ -212,3 +212,91 @@ $color-control-label-height: 20px; .wp-block-navigation .block-editor-button-block-appender { justify-content: flex-start; } + + +/** + * Setup state + */ + +// Unselected state. +.wp-block-navigation-placeholder__preview { + min-height: $grid-unit-10 + $grid-unit-10 + $button-size; + display: flex; + flex-direction: row; + align-items: center; + + // Hide when selected. + .is-selected & { + display: none; + } + + // Style skeleton elements. + // Needs specificity. + .wp-block-navigation-link.wp-block-navigation-link { + border-radius: $radius-block-ui; + background: currentColor; + min-width: 72px; + height: $grid-unit-20; + margin: $grid-unit-15 $grid-unit-30 $grid-unit-15 0; + } + + .wp-block-navigation-link.wp-block-navigation-link, + svg { + opacity: 0.3; + } +} + +// Selected state. +.wp-block-navigation-placeholder__controls { + padding: $grid-unit-10; + border-radius: $radius-block-ui; + background-color: $white; + box-shadow: inset 0 0 0 $border-width $gray-900; + flex-direction: row; + align-items: center; + display: none; + + // Show when selected. + .is-selected & { + display: flex; + } + + // Vertical navigation. + .is-vertical & { + .wp-block-navigation-placeholder__actions { + flex-direction: column; + } + } + + // Both selected and vertical. + .is-selected.is-vertical & { + display: inline-flex; // This makes the white box not take up all available space. + } + + .wp-block-navigation-placeholder__icon { + margin-right: $grid-unit-15; + height: $button-size; // Prevents jumpiness. + } +} + +// Both, when block is vertical. +.wp-block-navigation-placeholder__preview, +.wp-block-navigation-placeholder__controls { + .is-vertical & { + flex-direction: column; + align-items: flex-start; + min-height: $icon-size + ($grid-unit-20 + $grid-unit-15 + $grid-unit-15) * 3; + } +} + + +.wp-block-navigation-placeholder__actions { + display: flex; + font-size: $default-font-size; + + .components-button.components-dropdown-menu__toggle.has-icon { + padding: ($grid-unit-15 / 2) $grid-unit-15; + display: flex; + flex-direction: row-reverse; // This puts the chevron, which is hidden from screen readers, on the right. + } +} diff --git a/packages/block-library/src/navigation/index.js b/packages/block-library/src/navigation/index.js index 3139a88c908ff2..322d7f5cd017e5 100644 --- a/packages/block-library/src/navigation/index.js +++ b/packages/block-library/src/navigation/index.js @@ -11,6 +11,7 @@ import metadata from './block.json'; import edit from './edit'; import save from './save'; import deprecated from './deprecated'; +import variations from './variations'; const { name } = metadata; @@ -18,31 +19,12 @@ export { metadata, name }; export const settings = { title: __( 'Navigation' ), - icon, - description: __( 'A collection of blocks that allow visitors to get around your site.' ), - keywords: [ __( 'menu' ), __( 'navigation' ), __( 'links' ) ], - - variations: [ - { - name: 'horizontal', - isDefault: true, - title: __( 'Navigation (horizontal)' ), - description: __( 'Links shown in a row.' ), - attributes: { orientation: 'horizontal' }, - }, - { - name: 'vertical', - title: __( 'Navigation (vertical)' ), - description: __( 'Links shown in a column.' ), - attributes: { orientation: 'vertical' }, - }, - ], - + variations, example: { innerBlocks: [ { @@ -71,10 +53,7 @@ export const settings = { }, ], }, - edit, - save, - deprecated, }; diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index b56ea6023be975..57e312c11f3ed5 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -139,10 +139,12 @@ function render_block_core_navigation( $attributes, $content, $block ) { $inner_blocks_html .= $inner_block->render(); } + $block_styles = isset( $attributes['styles'] ) ? $attributes['styles'] : ''; + $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ), - 'style' => $colors['inline_styles'] . $font_sizes['inline_styles'], + 'style' => $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles'], ) ); @@ -169,3 +171,34 @@ function register_block_core_navigation() { } add_action( 'init', 'register_block_core_navigation' ); + +/** + * Filter that changes the parsed attribute values of navigation blocks contain typographic presets to contain the values directly. + * + * @param array $parsed_block The block being rendered. + * @return array The block being rendered without typographic presets. + */ +function block_core_navigation_typographic_presets_backcompatibility( $parsed_block ) { + if ( 'core/navigation' === $parsed_block['blockName'] ) { + $attribute_to_prefix_map = array( + 'fontStyle' => 'var:preset|font-style|', + 'fontWeight' => 'var:preset|font-weight|', + 'textDecoration' => 'var:preset|text-decoration|', + 'textTransform' => 'var:preset|text-transform|', + ); + foreach ( $attribute_to_prefix_map as $style_attribute => $prefix ) { + if ( ! empty( $parsed_block['attrs']['style']['typography'][ $style_attribute ] ) ) { + $prefix_len = strlen( $prefix ); + $attribute_value = &$parsed_block['attrs']['style']['typography'][ $style_attribute ]; + if ( 0 === strncmp( $attribute_value, $prefix, $prefix_len ) ) { + $attribute_value = substr( $attribute_value, $prefix_len ); + } + if ( 'textDecoration' === $style_attribute && 'strikethrough' === $attribute_value ) { + $attribute_value = 'line-through'; + } + } + } + } + return $parsed_block; +} +add_filter( 'render_block_data', 'block_core_navigation_typographic_presets_backcompatibility' ); diff --git a/packages/block-library/src/navigation/placeholder.js b/packages/block-library/src/navigation/placeholder.js index 79739656ba9e1d..5a3bb3bff98c6b 100644 --- a/packages/block-library/src/navigation/placeholder.js +++ b/packages/block-library/src/navigation/placeholder.js @@ -2,7 +2,6 @@ * External dependencies */ import { some } from 'lodash'; -import classnames from 'classnames'; /** * WordPress dependencies @@ -10,70 +9,26 @@ import classnames from 'classnames'; import { createBlock, parse } from '@wordpress/blocks'; import { Button, - CustomSelectControl, + DropdownMenu, + MenuGroup, + MenuItem, Spinner, - Placeholder, } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; import { forwardRef, useCallback, - useMemo, useState, useEffect, } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { navigation as icon } from '@wordpress/icons'; +import { Icon, chevronDown, search } from '@wordpress/icons'; /** * Internal dependencies */ import createDataTree from './create-data-tree'; -const CREATE_EMPTY_OPTION_VALUE = '__CREATE_EMPTY__'; -const CREATE_FROM_PAGES_OPTION_VALUE = '__CREATE_FROM_PAGES__'; - -/** - * Get instruction text for the Placeholder component. - * - * @param {boolean} hasMenus Flag that indicates if there are menus. - * @param {boolean} hasPages Flag that indicates if there are pages. - * - * @return {string} Text to display as the placeholder instructions. - */ -function getPlaceholderInstructions( hasMenus, hasPages ) { - if ( hasMenus && hasPages ) { - return __( - 'Use an existing menu here, include all top-level pages, or add an empty Navigation block.' - ); - } else if ( hasMenus && ! hasPages ) { - return __( - 'Use an existing menu here, or add an empty Navigation block.' - ); - } else if ( ! hasMenus && hasPages ) { - return __( - 'Include all existing pages here, or add an empty Navigation block.' - ); - } - - return __( 'Create an empty navigation.' ); -} - -/** - * Return the menu id if the user has one selected. - * - * @param {Object} selectedCreateOption An object containing details of - * the selected create option. - * - * @return {number|undefined} The menu id. - */ -function getSelectedMenu( selectedCreateOption ) { - const optionId = selectedCreateOption?.id; - return optionId !== undefined && Number.isInteger( optionId ) - ? optionId - : undefined; -} - /** * A recursive function that maps menu item nodes to blocks. * @@ -165,7 +120,7 @@ function convertPagesToBlocks( pages ) { } function NavigationPlaceholder( { onCreate }, ref ) { - const [ selectedCreateOption, setSelectedCreateOption ] = useState(); + const [ selectedMenu, setSelectedMenu ] = useState(); const [ isCreatingFromMenu, setIsCreatingFromMenu ] = useState( false ); @@ -198,7 +153,6 @@ function NavigationPlaceholder( { onCreate }, ref ) { }, ]; const menusParameters = [ { per_page: -1 } ]; - const selectedMenu = getSelectedMenu( selectedCreateOption ); const hasSelectedMenu = selectedMenu !== undefined; const menuItemsParameters = hasSelectedMenu ? [ @@ -236,76 +190,39 @@ function NavigationPlaceholder( { onCreate }, ref ) { : false, }; }, - [ selectedCreateOption ] + [ selectedMenu ] ); const hasPages = !! ( hasResolvedPages && pages?.length ); const hasMenus = !! ( hasResolvedMenus && menus?.length ); const isLoading = isResolvingPages || isResolvingMenus; - const createOptions = useMemo( - () => [ - ...( hasMenus ? menus : [] ), - { - id: CREATE_EMPTY_OPTION_VALUE, - name: __( 'Create empty Navigation' ), - className: 'is-create-empty-option', - }, - ...( hasPages - ? [ - { - id: CREATE_FROM_PAGES_OPTION_VALUE, - name: __( 'Create from all top-level pages' ), - }, - ] - : [] ), - ], - [ menus, hasMenus, hasPages ] - ); - const createFromMenu = useCallback( () => { - // If an empty menu was selected, create an empty block. - if ( ! menuItems.length ) { - onCreate( [] ); - return; - } - const blocks = convertMenuItemsToBlocks( menuItems ); const selectNavigationBlock = true; onCreate( blocks, selectNavigationBlock ); } ); - const onCreateButtonClick = useCallback( () => { - if ( ! selectedCreateOption ) { + const onCreateFromMenu = () => { + // If we have menu items, create the block right away. + if ( hasResolvedMenuItems ) { + createFromMenu(); return; } - const { key } = selectedCreateOption; - switch ( key ) { - case CREATE_EMPTY_OPTION_VALUE: { - onCreate( [] ); - return; - } + // Otherwise, create the block when resolution finishes. + setIsCreatingFromMenu( true ); + }; - case CREATE_FROM_PAGES_OPTION_VALUE: { - const blocks = convertPagesToBlocks( pages ); - const selectNavigationBlock = true; - onCreate( blocks, selectNavigationBlock ); - return; - } - - // The default case indicates that a menu was selected. - default: - // If we have menu items, create the block right away. - if ( hasResolvedMenuItems ) { - createFromMenu(); - return; - } + const onCreateEmptyMenu = () => { + onCreate( [] ); + }; - // Otherwise, create the block when resolution finishes. - setIsCreatingFromMenu( true ); - } - } ); + const onCreateAllPages = () => { + const blocks = convertPagesToBlocks( pages ); + const selectNavigationBlock = true; + onCreate( blocks, selectNavigationBlock ); + }; useEffect( () => { // If the user selected a menu but we had to wait for menu items to @@ -316,72 +233,66 @@ function NavigationPlaceholder( { onCreate }, ref ) { } }, [ isCreatingFromMenu, hasResolvedMenuItems ] ); - if ( hasMenus && ! selectedCreateOption ) { - setSelectedCreateOption( createOptions[ 0 ] ); - } - return ( - <Placeholder - className="wp-block-navigation-placeholder" - icon={ icon } - label={ __( 'Navigation' ) } - > - { isLoading && ( - <div ref={ ref }> - <Spinner /> { __( 'Loading…' ) } - </div> - ) } - { ! isLoading && ( - <div - ref={ ref } - className="wp-block-navigation-placeholder__actions" - > - <> - <CustomSelectControl - className={ classnames( - 'wp-block-navigation-placeholder__select-control', - { - 'has-menus': hasMenus, - } - ) } - label={ - ! isLoading - ? getPlaceholderInstructions( - hasMenus, - hasPages - ) - : undefined - } - value={ selectedCreateOption || createOptions[ 0 ] } - onChange={ ( { selectedItem } ) => { - if ( - selectedItem?.key === selectedCreateOption - ) { - return; - } - setSelectedCreateOption( selectedItem ); - setIsCreatingFromMenu( false ); - } } - options={ createOptions.map( ( option ) => { - return { - ...option, - key: option.id, - }; - } ) } - /> - <Button - isSecondary - className="wp-block-navigation-placeholder__button" - disabled={ ! selectedCreateOption } - isBusy={ isCreatingFromMenu } - onClick={ onCreateButtonClick } - > - { __( 'Create' ) } + <div className="wp-block-navigation-placeholder"> + <div className="wp-block-navigation-placeholder__preview"> + <span className="wp-block-navigation-link"></span> + <span className="wp-block-navigation-link"></span> + <span className="wp-block-navigation-link"></span> + <Icon icon={ search } /> + </div> + + <div className="wp-block-navigation-placeholder__controls"> + { isLoading && ( + <div ref={ ref }> + <Spinner /> + </div> + ) } + { ! isLoading && ( + <div + ref={ ref } + className="wp-block-navigation-placeholder__actions" + > + { hasMenus ? ( + <DropdownMenu + text={ __( 'Existing menu' ) } + icon={ chevronDown } + className="wp-block-navigation-placeholder__actions__dropdown" + > + { ( { onClose } ) => ( + <MenuGroup> + { menus.map( ( menu ) => { + return ( + <MenuItem + onClick={ () => { + setSelectedMenu( + menu.id + ); + onCreateFromMenu(); + } } + onClose={ onClose } + key={ menu.id } + > + { menu.name } + </MenuItem> + ); + } ) } + </MenuGroup> + ) } + </DropdownMenu> + ) : undefined } + { hasPages ? ( + <Button onClick={ onCreateAllPages }> + { __( 'Add all pages' ) } + </Button> + ) : undefined } + <Button onClick={ onCreateEmptyMenu }> + { __( 'Start empty' ) } </Button> - </> - </div> - ) } - </Placeholder> + </div> + ) } + </div> + </div> ); } diff --git a/packages/block-library/src/navigation/variations.js b/packages/block-library/src/navigation/variations.js new file mode 100644 index 00000000000000..307fa8c0c12048 --- /dev/null +++ b/packages/block-library/src/navigation/variations.js @@ -0,0 +1,24 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +const variations = [ + { + name: 'horizontal', + isDefault: true, + title: __( 'Navigation (horizontal)' ), + description: __( 'Links shown in a row.' ), + attributes: { orientation: 'horizontal' }, + scope: [ 'inserter', 'transform' ], + }, + { + name: 'vertical', + title: __( 'Navigation (vertical)' ), + description: __( 'Links shown in a column.' ), + attributes: { orientation: 'vertical' }, + scope: [ 'inserter', 'transform' ], + }, +]; + +export default variations; diff --git a/packages/block-library/src/nextpage/block.json b/packages/block-library/src/nextpage/block.json index 2236627dbb7e01..f1a8a2745cb572 100644 --- a/packages/block-library/src/nextpage/block.json +++ b/packages/block-library/src/nextpage/block.json @@ -7,5 +7,6 @@ "customClassName": false, "className": false, "html": false - } + }, + "editorStyle": "wp-block-nextpage-editor" } diff --git a/packages/block-library/src/nextpage/editor.scss b/packages/block-library/src/nextpage/editor.scss index 13458ca48cb96e..11c2c2bbf3b253 100644 --- a/packages/block-library/src/nextpage/editor.scss +++ b/packages/block-library/src/nextpage/editor.scss @@ -1,5 +1,6 @@ .block-editor-block-list__block[data-type="core/nextpage"] { max-width: 100%; + text-align: center; margin-top: $default-block-margin; margin-bottom: $default-block-margin; } @@ -13,7 +14,6 @@ > span { font-size: $default-font-size; position: relative; - display: inline-block; text-transform: uppercase; font-weight: 600; font-family: $default-font; diff --git a/packages/block-library/src/paragraph/block.json b/packages/block-library/src/paragraph/block.json index 44118c165c2770..a29e22c01d5af3 100644 --- a/packages/block-library/src/paragraph/block.json +++ b/packages/block-library/src/paragraph/block.json @@ -37,5 +37,7 @@ "lineHeight": true, "__experimentalSelector": "p", "__unstablePasteTextInline": true - } + }, + "editorStyle": "wp-block-paragraph-editor", + "style": "wp-block-paragraph" } diff --git a/packages/block-library/src/post-author/block.json b/packages/block-library/src/post-author/block.json index 99f1cd2c8a8163..8a4d8d66dbb670 100644 --- a/packages/block-library/src/post-author/block.json +++ b/packages/block-library/src/post-author/block.json @@ -33,5 +33,7 @@ "link": true }, "lineHeight": true - } + }, + "editorStyle": "wp-block-post-author-editor", + "style": "wp-block-post-author" } diff --git a/packages/block-library/src/post-author/edit.js b/packages/block-library/src/post-author/edit.js index 120fb0da34616a..b26c4269d7f33a 100644 --- a/packages/block-library/src/post-author/edit.js +++ b/packages/block-library/src/post-author/edit.js @@ -65,21 +65,28 @@ function PostAuthorEdit( { isSelected, context, attributes, setAttributes } ) { <> <InspectorControls> <PanelBody title={ __( 'Author Settings' ) }> - <SelectControl - label={ __( 'Author' ) } - value={ authorId } - options={ authors.map( ( { id, name } ) => { - return { - value: id, - label: name, - }; - } ) } - onChange={ ( nextAuthorId ) => { - editEntityRecord( 'postType', postType, postId, { - author: nextAuthorId, - } ); - } } - /> + { !! authors?.length && ( + <SelectControl + label={ __( 'Author' ) } + value={ authorId } + options={ authors.map( ( { id, name } ) => { + return { + value: id, + label: name, + }; + } ) } + onChange={ ( nextAuthorId ) => { + editEntityRecord( + 'postType', + postType, + postId, + { + author: nextAuthorId, + } + ); + } } + /> + ) } <ToggleControl label={ __( 'Show avatar' ) } checked={ showAvatar } @@ -137,7 +144,8 @@ function PostAuthorEdit( { isSelected, context, attributes, setAttributes } ) { <RichText className="wp-block-post-author__byline" multiline={ false } - placeholder={ __( 'Write byline …' ) } + aria-label={ __( 'Post author byline text' ) } + placeholder={ __( 'Write byline…' ) } value={ byline } onChange={ ( value ) => setAttributes( { byline: value } ) diff --git a/packages/block-library/src/post-comment/edit.js b/packages/block-library/src/post-comment/edit.js index 7a65156043f0ab..3acab9af7c0772 100644 --- a/packages/block-library/src/post-comment/edit.js +++ b/packages/block-library/src/post-comment/edit.js @@ -24,7 +24,9 @@ export default function Edit( { attributes, setAttributes } ) { <Placeholder icon={ blockDefault } label={ __( 'Post Comment' ) } - instructions={ __( 'Input post comment ID' ) } + instructions={ __( + 'To show a comment, input the comment ID.' + ) } > <TextControl value={ commentId } diff --git a/packages/block-library/src/post-comments-form/block.json b/packages/block-library/src/post-comments-form/block.json index 64cdcbba8f95fd..61c339f5012879 100644 --- a/packages/block-library/src/post-comments-form/block.json +++ b/packages/block-library/src/post-comments-form/block.json @@ -19,5 +19,6 @@ }, "fontSize": true, "lineHeight": true - } + }, + "style": "wp-block-post-comments-form" } diff --git a/packages/block-library/src/post-comments-form/style.scss b/packages/block-library/src/post-comments-form/style.scss new file mode 100644 index 00000000000000..bd2e9c10ec5cdb --- /dev/null +++ b/packages/block-library/src/post-comments-form/style.scss @@ -0,0 +1,24 @@ +$blocks-button__height: 3.1em; + +// Styles copied from button block styles. +.wp-block-post-comments-form input[type="submit"] { + color: $white; + background-color: #32373c; + border: none; + border-radius: $blocks-button__height / 2; + box-shadow: none; + cursor: pointer; + display: inline-block; + font-size: 1.125em; + padding: 0.667em 1.333em; + text-align: center; + text-decoration: none; + overflow-wrap: break-word; + + &:hover, + &:focus, + &:active, + &:visited { + color: $white; + } +} diff --git a/packages/block-library/src/post-content/block.json b/packages/block-library/src/post-content/block.json index 9eb24596ec59fe..1869af668fd839 100644 --- a/packages/block-library/src/post-content/block.json +++ b/packages/block-library/src/post-content/block.json @@ -9,5 +9,6 @@ "supports": { "align": [ "wide", "full" ], "html": false - } + }, + "editorStyle": "wp-block-post-content-editor" } diff --git a/packages/block-library/src/post-content/edit.js b/packages/block-library/src/post-content/edit.js index 855383fe19c4cd..d4c86853533d89 100644 --- a/packages/block-library/src/post-content/edit.js +++ b/packages/block-library/src/post-content/edit.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { useSelect } from '@wordpress/data'; import { useBlockProps } from '@wordpress/block-editor'; /** @@ -13,19 +12,8 @@ import PostContentInnerBlocks from './inner-blocks'; export default function PostContentEdit( { context: { postId: contextPostId, postType: contextPostType }, } ) { - const { id: currentPostId, type: currentPostType } = useSelect( - ( select ) => select( 'core/editor' ).getCurrentPost() ?? {} - ); const blockProps = useBlockProps(); - - // Only render InnerBlocks if the context is different from the active post - // to avoid infinite recursion of post content. - if ( - contextPostId && - contextPostType && - contextPostId !== currentPostId && - contextPostType !== currentPostType - ) { + if ( contextPostId && contextPostType ) { return ( <div { ...blockProps }> <PostContentInnerBlocks diff --git a/packages/block-library/src/post-excerpt/block.json b/packages/block-library/src/post-excerpt/block.json index f45971f7746367..2e2690d398625f 100644 --- a/packages/block-library/src/post-excerpt/block.json +++ b/packages/block-library/src/post-excerpt/block.json @@ -30,5 +30,6 @@ "link": true }, "lineHeight": true - } + }, + "editorStyle": "wp-block-post-excerpt-editor" } diff --git a/packages/block-library/src/post-excerpt/edit.js b/packages/block-library/src/post-excerpt/edit.js index d8b0310e0af51f..126c38418d49ac 100644 --- a/packages/block-library/src/post-excerpt/edit.js +++ b/packages/block-library/src/post-excerpt/edit.js @@ -103,22 +103,20 @@ function PostExcerptEditor( { ! showMoreOnNewLine && 'wp-block-post-excerpt__excerpt is-inline' } - placeholder={ postContentExcerpt } + aria-label={ __( 'Post excerpt text' ) } value={ excerpt || - ( isSelected - ? '' - : postContentExcerpt || - __( 'No post excerpt found' ) ) + postContentExcerpt || + ( isSelected ? '' : __( 'No post excerpt found' ) ) } onChange={ setExcerpt } - keepPlaceholderOnFocus /> { ! showMoreOnNewLine && ' ' } { showMoreOnNewLine ? ( <p className="wp-block-post-excerpt__more-text"> <RichText tagName="a" + aria-label={ __( 'Read more link text' ) } placeholder={ __( 'Read more…' ) } value={ moreText } onChange={ ( newMoreText ) => @@ -129,6 +127,7 @@ function PostExcerptEditor( { ) : ( <RichText tagName="a" + aria-label={ __( 'Read more link text' ) } placeholder={ __( 'Read more…' ) } value={ moreText } onChange={ ( newMoreText ) => diff --git a/packages/block-library/src/post-excerpt/index.php b/packages/block-library/src/post-excerpt/index.php index a6a4c641dc0103..cec543d8110935 100644 --- a/packages/block-library/src/post-excerpt/index.php +++ b/packages/block-library/src/post-excerpt/index.php @@ -36,7 +36,7 @@ function render_block_core_post_excerpt( $attributes, $content, $block ) { $output = sprintf( '<div %1$s>', esc_attr( $wrapper_attributes ) ) . '<p class="wp-block-post-excerpt__excerpt">' . get_the_excerpt( $block->context['postId'] ); if ( ! isset( $attributes['showMoreOnNewLine'] ) || $attributes['showMoreOnNewLine'] ) { - $output .= '</p>' . '<p class="wp-block-post-excerpt__more-text">' . $more_text . '</p>'; + $output .= '</p>' . '<p class="wp-block-post-excerpt__more-text">' . $more_text . '</p></div>'; } else { $output .= ' ' . $more_text . '</p>' . '</div>'; } diff --git a/packages/block-library/src/post-featured-image/block.json b/packages/block-library/src/post-featured-image/block.json index 0c183bf7d0e235..f3a2deb587122e 100644 --- a/packages/block-library/src/post-featured-image/block.json +++ b/packages/block-library/src/post-featured-image/block.json @@ -13,6 +13,9 @@ "postType" ], "supports": { + "align": [ "left", "right", "center", "wide", "full" ], "html": false - } + }, + "editorStyle": "wp-block-post-featured-image-editor", + "style": "wp-block-post-featured-image" } diff --git a/packages/block-library/src/post-featured-image/edit.js b/packages/block-library/src/post-featured-image/edit.js index 35a6650585f8cd..c85faf7a9dddf7 100644 --- a/packages/block-library/src/post-featured-image/edit.js +++ b/packages/block-library/src/post-featured-image/edit.js @@ -3,14 +3,27 @@ */ import { useEntityProp } from '@wordpress/core-data'; import { useSelect } from '@wordpress/data'; -import { Icon, ToggleControl, PanelBody } from '@wordpress/components'; +import { + Icon, + ToggleControl, + PanelBody, + withNotices, +} from '@wordpress/components'; +import { + InspectorControls, + BlockControls, + MediaPlaceholder, + MediaReplaceFlow, + BlockIcon, + useBlockProps, +} from '@wordpress/block-editor'; import { __, sprintf } from '@wordpress/i18n'; -import { postFeaturedImage as icon } from '@wordpress/icons'; -import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; +import { postFeaturedImage } from '@wordpress/icons'; +const ALLOWED_MEDIA_TYPES = [ 'image' ]; const placeholderChip = ( <div className="post-featured-image_placeholder"> - <Icon icon={ icon } /> + <Icon icon={ postFeaturedImage } /> <p> { __( 'Featured Image' ) }</p> </div> ); @@ -19,8 +32,10 @@ function PostFeaturedImageDisplay( { attributes: { isLink }, setAttributes, context: { postId, postType }, + noticeUI, + noticeOperations, } ) { - const [ featuredImage ] = useEntityProp( + const [ featuredImage, setFeaturedImage ] = useEntityProp( 'postType', postType, 'featured_media', @@ -31,14 +46,44 @@ function PostFeaturedImageDisplay( { featuredImage && select( 'core' ).getMedia( featuredImage ), [ featuredImage ] ); - const image = ! media ? ( - placeholderChip - ) : ( - <img - src={ media.source_url } - alt={ media.alt_text || __( 'No alternative text set' ) } - /> - ); + const onSelectImage = ( value ) => { + if ( value?.id ) { + setFeaturedImage( value.id ); + } + }; + function onUploadError( message ) { + noticeOperations.removeAllNotices(); + noticeOperations.createErrorNotice( message ); + } + let image; + if ( ! featuredImage ) { + image = ( + <MediaPlaceholder + icon={ <BlockIcon icon={ postFeaturedImage } /> } + onSelect={ onSelectImage } + notices={ noticeUI } + onError={ onUploadError } + accept="image/*" + allowedTypes={ ALLOWED_MEDIA_TYPES } + labels={ { + title: __( 'Featured image' ), + instructions: __( + 'Upload a media file or pick one from your media library.' + ), + } } + /> + ); + } else { + // We have a Featured image so show a Placeholder if is loading. + image = ! media ? ( + placeholderChip + ) : ( + <img + src={ media.source_url } + alt={ media.alt_text || __( 'Featured image' ) } + /> + ); + } return ( <> @@ -55,14 +100,28 @@ function PostFeaturedImageDisplay( { /> </PanelBody> </InspectorControls> + <BlockControls> + { !! media && ( + <MediaReplaceFlow + mediaId={ featuredImage } + mediaURL={ media.source_url } + allowedTypes={ ALLOWED_MEDIA_TYPES } + accept="image/*" + onSelect={ onSelectImage } + onError={ onUploadError } + /> + ) } + </BlockControls> <div { ...useBlockProps() }>{ image }</div> </> ); } +const PostFeaturedImageWithNotices = withNotices( PostFeaturedImageDisplay ); + export default function PostFeaturedImageEdit( props ) { if ( ! props.context?.postId ) { return placeholderChip; } - return <PostFeaturedImageDisplay { ...props } />; + return <PostFeaturedImageWithNotices { ...props } />; } diff --git a/packages/block-library/src/post-featured-image/editor.scss b/packages/block-library/src/post-featured-image/editor.scss index 35faa55448736c..1f9cbb8e718a6e 100644 --- a/packages/block-library/src/post-featured-image/editor.scss +++ b/packages/block-library/src/post-featured-image/editor.scss @@ -1,4 +1,12 @@ div[data-type="core/post-featured-image"] { + img { + max-width: 100%; + height: auto; + display: block; + } +} + +.editor-styles-wrapper { .post-featured-image_placeholder { display: flex; flex-direction: row; @@ -16,9 +24,4 @@ div[data-type="core/post-featured-image"] { margin: 0; } } - img { - max-width: 100%; - height: auto; - display: block; - } } diff --git a/packages/block-library/src/post-featured-image/style.scss b/packages/block-library/src/post-featured-image/style.scss index eaa2b5f4137330..1deddff9a9fec2 100644 --- a/packages/block-library/src/post-featured-image/style.scss +++ b/packages/block-library/src/post-featured-image/style.scss @@ -2,4 +2,8 @@ a { display: inline-block; } + img { + max-width: 100%; + height: auto; + } } diff --git a/packages/block-library/src/post-hierarchical-terms/edit.js b/packages/block-library/src/post-hierarchical-terms/edit.js index fda083bf96e4b2..a3fb6459a34e3e 100644 --- a/packages/block-library/src/post-hierarchical-terms/edit.js +++ b/packages/block-library/src/post-hierarchical-terms/edit.js @@ -14,6 +14,7 @@ import { useBlockProps, __experimentalBlockVariationPicker as BlockVariationPicker, } from '@wordpress/block-editor'; +import { store as blocksStore } from '@wordpress/blocks'; import { Spinner } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; @@ -39,7 +40,7 @@ export default function PostHierarchicalTermsEdit( { getBlockVariations, getBlockType, getDefaultBlockVariation, - } = select( 'core/blocks' ); + } = select( blocksStore ); return { blockType: getBlockType( name ), diff --git a/packages/block-library/src/post-hierarchical-terms/index.php b/packages/block-library/src/post-hierarchical-terms/index.php index bf26204b57e654..a83e69d84daa61 100644 --- a/packages/block-library/src/post-hierarchical-terms/index.php +++ b/packages/block-library/src/post-hierarchical-terms/index.php @@ -19,6 +19,9 @@ function render_block_core_post_hierarchical_terms( $attributes, $content, $bloc } $post_hierarchical_terms = get_the_terms( $block->context['postId'], $attributes['term'] ); + if ( is_wp_error( $post_hierarchical_terms ) ) { + return ''; + } if ( empty( $post_hierarchical_terms ) ) { return ''; } diff --git a/packages/block-library/src/post-hierarchical-terms/variations.js b/packages/block-library/src/post-hierarchical-terms/variations.js index 8ff6dcc07f910f..48ef92afc10978 100644 --- a/packages/block-library/src/post-hierarchical-terms/variations.js +++ b/packages/block-library/src/post-hierarchical-terms/variations.js @@ -8,7 +8,7 @@ const variations = [ name: 'category', title: __( 'Post Categories' ), icon: 'category', - is_default: true, + isDefault: true, attributes: { term: 'category' }, }, ]; diff --git a/packages/block-library/src/post-title/block.json b/packages/block-library/src/post-title/block.json index 3c8c63031bcb2a..1c0f4adeb9c626 100644 --- a/packages/block-library/src/post-title/block.json +++ b/packages/block-library/src/post-title/block.json @@ -29,19 +29,57 @@ } }, "supports": { + "align": [ "wide", "full" ], "html": false, "color": { "gradients": true }, "fontSize": true, "lineHeight": true, + "__experimentalFontFamily": true, "__experimentalSelector": { - "core/post-title/h1": "h1", - "core/post-title/h2": "h2", - "core/post-title/h3": "h3", - "core/post-title/h4": "h4", - "core/post-title/h5": "h5", - "core/post-title/h6": "h6" + "core/post-title/h1": { + "title": "h1", + "selector": "h1.wp-block-post-title", + "attributes": { + "level": 1 + } + }, + "core/post-title/h2": { + "title": "h2", + "selector": "h2.wp-block-post-title", + "attributes": { + "level": 2 + } + }, + "core/post-title/h3": { + "title": "h3", + "selector": "h3.wp-block-post-title", + "attributes": { + "level": 3 + } + }, + "core/post-title/h4": { + "title": "h4", + "selector": "h4.wp-block-post-title", + "attributes": { + "level": 4 + } + }, + "core/post-title/h5": { + "title": "h5", + "selector": "h5.wp-block-post-title", + "attributes": { + "level": 5 + } + }, + "core/post-title/h6": { + "title": "h6", + "selector": "h6.wp-block-post-title", + "attributes": { + "level": 6 + } + } } } } diff --git a/packages/block-library/src/post-title/edit.js b/packages/block-library/src/post-title/edit.js index 46030a4ed4cec3..e7c5b39f8cfaeb 100644 --- a/packages/block-library/src/post-title/edit.js +++ b/packages/block-library/src/post-title/edit.js @@ -6,12 +6,13 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { AlignmentToolbar, BlockControls, InspectorControls, useBlockProps, + PlainText, } from '@wordpress/block-editor'; import { ToolbarGroup, @@ -42,6 +43,7 @@ export default function PostTitleEdit( { ), [ postType, postId ] ); + const { editEntityRecord } = useDispatch( 'core' ); const blockProps = useBlockProps( { className: classnames( { @@ -53,11 +55,33 @@ export default function PostTitleEdit( { return null; } - let title = post.title || __( 'Post Title' ); + const { title, link } = post; + + let titleElement = ( + <PlainText + tagName={ TagName } + placeholder={ __( 'No Title' ) } + value={ title } + onChange={ ( value ) => + editEntityRecord( 'postType', postType, postId, { + title: value, + } ) + } + __experimentalVersion={ 2 } + { ...( isLink ? {} : blockProps ) } + /> + ); + if ( isLink ) { - title = ( - <a href={ post.link } target={ linkTarget } rel={ rel }> - { title } + titleElement = ( + <a + href={ link } + target={ linkTarget } + rel={ rel } + onClick={ ( event ) => event.preventDefault() } + { ...blockProps } + > + { titleElement } </a> ); } @@ -109,7 +133,7 @@ export default function PostTitleEdit( { ) } </PanelBody> </InspectorControls> - <TagName { ...blockProps }>{ title }</TagName> + { titleElement } </> ); } diff --git a/packages/block-library/src/preformatted/block.json b/packages/block-library/src/preformatted/block.json index d235d33ccfdc59..6ed73534883ba0 100644 --- a/packages/block-library/src/preformatted/block.json +++ b/packages/block-library/src/preformatted/block.json @@ -12,6 +12,8 @@ } }, "supports": { - "anchor": true - } + "anchor": true, + "fontSize": true + }, + "style": "wp-block-preformatted" } diff --git a/packages/block-library/src/preformatted/edit.js b/packages/block-library/src/preformatted/edit.js index 5e802013f0643b..69ce4745de5269 100644 --- a/packages/block-library/src/preformatted/edit.js +++ b/packages/block-library/src/preformatted/edit.js @@ -8,6 +8,7 @@ export default function PreformattedEdit( { attributes, mergeBlocks, setAttributes, + onRemove, } ) { const { content } = attributes; const blockProps = useBlockProps(); @@ -23,6 +24,8 @@ export default function PreformattedEdit( { content: nextContent, } ); } } + onRemove={ onRemove } + aria-label={ __( 'Preformatted text' ) } placeholder={ __( 'Write preformatted text…' ) } onMerge={ mergeBlocks } { ...blockProps } diff --git a/packages/block-library/src/preformatted/edit.native.js b/packages/block-library/src/preformatted/edit.native.js index 88a7c56a79ff1c..e22574498c7cf7 100644 --- a/packages/block-library/src/preformatted/edit.native.js +++ b/packages/block-library/src/preformatted/edit.native.js @@ -12,7 +12,7 @@ import { withPreferredColorScheme } from '@wordpress/compose'; import WebPreformattedEdit from './edit.js'; import styles from './styles.scss'; -function PreformattedEdit( props ) { +export function PreformattedEdit( props ) { const { getStylesFromColorScheme } = props; const richTextStyle = getStylesFromColorScheme( styles.wpRichTextLight, diff --git a/packages/block-library/src/preformatted/style.scss b/packages/block-library/src/preformatted/style.scss new file mode 100644 index 00000000000000..d9309db9cf4a4e --- /dev/null +++ b/packages/block-library/src/preformatted/style.scss @@ -0,0 +1,3 @@ +.wp-block-preformatted { + white-space: pre-wrap; +} diff --git a/packages/block-library/src/preformatted/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/preformatted/test/__snapshots__/edit.native.js.snap new file mode 100644 index 00000000000000..9d729e151661dc --- /dev/null +++ b/packages/block-library/src/preformatted/test/__snapshots__/edit.native.js.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`core/more/edit/native should match snapshot when content is empty 1`] = ` +<View> + <PreformattedEdit + getStylesFromColorScheme={[Function]} + setAttributes={[MockFunction]} + /> +</View> +`; + +exports[`core/more/edit/native should match snapshot when content is not empty 1`] = ` +<View> + <PreformattedEdit + attributes={ + Object { + "content": "Hello World!", + } + } + getStylesFromColorScheme={[Function]} + setAttributes={[MockFunction]} + /> +</View> +`; diff --git a/packages/block-library/src/preformatted/test/edit.native.js b/packages/block-library/src/preformatted/test/edit.native.js new file mode 100644 index 00000000000000..5b72ad1e420324 --- /dev/null +++ b/packages/block-library/src/preformatted/test/edit.native.js @@ -0,0 +1,44 @@ +/** + * External dependencies + */ +import ShallowRenderer from 'react-test-renderer/shallow'; +const shallowRenderer = new ShallowRenderer(); + +/** + * Internal dependencies + */ +import { PreformattedEdit } from '../edit'; + +describe( 'core/more/edit/native', () => { + it( 'renders without crashing', () => { + shallowRenderer.render( + <PreformattedEdit + setAttributes={ jest.fn() } + getStylesFromColorScheme={ jest.fn() } + /> + ); + const element = shallowRenderer.getRenderOutput(); + expect( element.type ).toBeDefined(); + } ); + + it( 'should match snapshot when content is empty', () => { + shallowRenderer.render( + <PreformattedEdit + setAttributes={ jest.fn() } + getStylesFromColorScheme={ ( styles1 ) => styles1 } + /> + ); + expect( shallowRenderer.getRenderOutput() ).toMatchSnapshot(); + } ); + + it( 'should match snapshot when content is not empty', () => { + shallowRenderer.render( + <PreformattedEdit + attributes={ { content: 'Hello World!' } } + setAttributes={ jest.fn() } + getStylesFromColorScheme={ ( styles1 ) => styles1 } + /> + ); + expect( shallowRenderer.getRenderOutput() ).toMatchSnapshot(); + } ); +} ); diff --git a/packages/block-library/src/pullquote/block.json b/packages/block-library/src/pullquote/block.json index fa49196bec1d4f..e8a0fd00d52b97 100644 --- a/packages/block-library/src/pullquote/block.json +++ b/packages/block-library/src/pullquote/block.json @@ -36,5 +36,7 @@ "wide", "full" ] - } + }, + "editorStyle": "wp-block-pullquote-editor", + "style": "wp-block-pullquote" } diff --git a/packages/block-library/src/pullquote/edit.js b/packages/block-library/src/pullquote/edit.js index 8ba6075d753222..709f3745108741 100644 --- a/packages/block-library/src/pullquote/edit.js +++ b/packages/block-library/src/pullquote/edit.js @@ -124,6 +124,7 @@ function PullQuoteEdit( { value: nextValue, } ) } + aria-label={ __( 'Pullquote text' ) } placeholder={ // translators: placeholder text used for the quote __( 'Write quote…' ) @@ -134,6 +135,7 @@ function PullQuoteEdit( { <RichText identifier="citation" value={ citation } + aria-label={ __( 'Pullquote citation text' ) } placeholder={ // translators: placeholder text used for the citation __( 'Write citation…' ) diff --git a/packages/block-library/src/pullquote/theme.scss b/packages/block-library/src/pullquote/theme.scss index 53fbdd9e04b5cb..6b5e8401227f5b 100644 --- a/packages/block-library/src/pullquote/theme.scss +++ b/packages/block-library/src/pullquote/theme.scss @@ -1,13 +1,13 @@ .wp-block-pullquote { - border-top: 4px solid #555; - border-bottom: 4px solid #555; + border-top: 4px solid currentColor; + border-bottom: 4px solid currentColor; margin-bottom: 1.75em; - color: #555; + color: currentColor; cite, footer, &__citation { - color: #555; + color: currentColor; text-transform: uppercase; font-size: 0.8125em; font-style: normal; diff --git a/packages/block-library/src/query-loop/block.json b/packages/block-library/src/query-loop/block.json index 963afbe71c6300..6059febae1dd60 100644 --- a/packages/block-library/src/query-loop/block.json +++ b/packages/block-library/src/query-loop/block.json @@ -5,10 +5,13 @@ "usesContext": [ "queryId", "query", - "queryContext" + "queryContext", + "layout" ], "supports": { "reusable": false, "html": false - } + }, + "style": "wp-block-query-loop", + "editorStyle": "wp-block-query-loop-editor" } diff --git a/packages/block-library/src/query-loop/edit.js b/packages/block-library/src/query-loop/edit.js index aa2bdc38f58da5..4d62b24356bb59 100644 --- a/packages/block-library/src/query-loop/edit.js +++ b/packages/block-library/src/query-loop/edit.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -6,9 +11,9 @@ import { useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { BlockContextProvider, - InnerBlocks, BlockPreview, useBlockProps, + __experimentalUseInnerBlocksProps as useInnerBlocksProps, } from '@wordpress/block-editor'; /** @@ -35,8 +40,11 @@ export default function QueryLoopEdit( { author, search, exclude, + sticky, + inherit, } = {}, queryContext, + layout: { type: layoutType = 'flex', columns = 1 } = {}, }, } ) { const [ { page } ] = useQueryContext() || queryContext || [ {} ]; @@ -65,6 +73,36 @@ export default function QueryLoopEdit( { if ( exclude?.length ) { query.exclude = exclude; } + // If sticky is not set, it will return all posts in the results. + // If sticky is set to `only`, it will limit the results to sticky posts only. + // If it is anything else, it will exclude sticky posts from results. For the record the value stored is `exclude`. + if ( sticky ) { + query.sticky = sticky === 'only'; + } + + // When you insert this block outside of the edit site then store + // does not exist therefore we check for its existence. + if ( inherit && select( 'core/edit-site' ) ) { + // This should be passed from the context exposed by edit site. + const { getTemplateId, getTemplateType } = select( + 'core/edit-site' + ); + + if ( 'wp_template' === getTemplateType() ) { + const { slug } = select( 'core' ).getEntityRecord( + 'postType', + 'wp_template', + getTemplateId() + ); + + // Change the post-type if needed. + if ( slug?.startsWith( 'archive-' ) ) { + query.postType = slug.replace( 'archive-', '' ); + postType = query.postType; + } + } + } + return { posts: getEntityRecords( 'postType', postType, query ), blocks: getBlocks( clientId ), @@ -83,6 +121,8 @@ export default function QueryLoopEdit( { search, postType, exclude, + sticky, + inherit, ] ); @@ -94,7 +134,14 @@ export default function QueryLoopEdit( { } ) ), [ posts ] ); - const blockProps = useBlockProps(); + const hasLayoutFlex = layoutType === 'flex' && columns > 1; + const blockProps = useBlockProps( { + className: classnames( { + 'is-flex-container': hasLayoutFlex, + [ `columns-${ columns }` ]: hasLayoutFlex, + } ), + } ); + const innerBlocksProps = useInnerBlocksProps( {}, { template: TEMPLATE } ); if ( ! posts ) { return <p { ...blockProps }>{ __( 'Loading…' ) }</p>; @@ -105,7 +152,7 @@ export default function QueryLoopEdit( { } return ( - <div { ...blockProps }> + <ul { ...blockProps }> { blockContexts && blockContexts.map( ( blockContext ) => ( <BlockContextProvider @@ -114,21 +161,20 @@ export default function QueryLoopEdit( { > { blockContext === ( activeBlockContext || blockContexts[ 0 ] ) ? ( - <InnerBlocks - template={ TEMPLATE } - templateInsertUpdatesSelection={ false } - /> + <li { ...innerBlocksProps } /> ) : ( - <BlockPreview - blocks={ blocks } - __experimentalLive - __experimentalOnClick={ () => - setActiveBlockContext( blockContext ) - } - /> + <li> + <BlockPreview + blocks={ blocks } + __experimentalLive + __experimentalOnClick={ () => + setActiveBlockContext( blockContext ) + } + /> + </li> ) } </BlockContextProvider> ) ) } - </div> + </ul> ); } diff --git a/packages/block-library/src/query-loop/editor.scss b/packages/block-library/src/query-loop/editor.scss index 294eb615109be8..39a614277d58d7 100644 --- a/packages/block-library/src/query-loop/editor.scss +++ b/packages/block-library/src/query-loop/editor.scss @@ -1,3 +1,5 @@ -.editor-styles-wrapper .wp-block.wp-block-query-loop { +.wp-block.wp-block-query-loop { max-width: 100%; + padding-left: 0; + list-style: none; } diff --git a/packages/block-library/src/query-loop/index.js b/packages/block-library/src/query-loop/index.js index 3aa9deb41c8139..2995100a1b874f 100644 --- a/packages/block-library/src/query-loop/index.js +++ b/packages/block-library/src/query-loop/index.js @@ -19,4 +19,5 @@ export const settings = { icon: loop, edit, save, + parent: [ 'core/query' ], }; diff --git a/packages/block-library/src/query-loop/index.php b/packages/block-library/src/query-loop/index.php index a6e619f0841f7a..f611e4fbd81d70 100644 --- a/packages/block-library/src/query-loop/index.php +++ b/packages/block-library/src/query-loop/index.php @@ -19,21 +19,31 @@ function render_block_core_query_loop( $attributes, $content, $block ) { $page = empty( $_GET[ $page_key ] ) ? 1 : filter_var( $_GET[ $page_key ], FILTER_VALIDATE_INT ); $query = array( - 'post_type' => 'post', - 'offset' => 0, - 'order' => 'DESC', - 'orderby' => 'date', + 'post_type' => 'post', + 'offset' => 0, + 'order' => 'DESC', + 'orderby' => 'date', + 'post__not_in' => array(), ); if ( isset( $block->context['query'] ) ) { if ( isset( $block->context['query']['postType'] ) ) { $query['post_type'] = $block->context['query']['postType']; } + if ( isset( $block->context['query']['sticky'] ) && ! empty( $block->context['query']['sticky'] ) ) { + $sticky = get_option( 'sticky_posts' ); + if ( 'only' === $block->context['query']['sticky'] ) { + $query['post__in'] = $sticky; + } else { + $query['post__not_in'] = array_merge( $query['post__not_in'], $sticky ); + } + } if ( isset( $block->context['query']['exclude'] ) ) { - $query['post__not_in'] = $block->context['query']['exclude']; + $query['post__not_in'] = array_merge( $query['post__not_in'], $block->context['query']['exclude'] ); } if ( isset( $block->context['query']['perPage'] ) ) { - $query['offset'] = ( $block->context['query']['perPage'] * ( $page - 1 ) ) + $block->context['query']['offset']; + $query['offset'] = ( $block->context['query']['perPage'] * ( $page - 1 ) ) + $block->context['query']['offset']; + $query['posts_per_page'] = $block->context['query']['perPage']; } if ( isset( $block->context['query']['categoryIds'] ) ) { $query['category__in'] = $block->context['query']['categoryIds']; @@ -47,9 +57,6 @@ function render_block_core_query_loop( $attributes, $content, $block ) { if ( isset( $block->context['query']['orderBy'] ) ) { $query['orderby'] = $block->context['query']['orderBy']; } - if ( isset( $block->context['query']['perPage'] ) ) { - $query['posts_per_page'] = $block->context['query']['perPage']; - } if ( isset( $block->context['query']['author'] ) ) { $query['author'] = $block->context['query']['author']; } @@ -58,11 +65,29 @@ function render_block_core_query_loop( $attributes, $content, $block ) { } } + // Override the custom query with the global query if needed. + $use_global_query = ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] ); + if ( $use_global_query ) { + global $wp_query; + if ( $wp_query && isset( $wp_query->query_vars ) && is_array( $wp_query->query_vars ) ) { + $query = wp_parse_args( $wp_query->query_vars, $query ); + } + } + $posts = get_posts( $query ); + $classnames = ''; + if ( isset( $block->context['layout'] ) && isset( $block->context['query'] ) ) { + if ( isset( $block->context['layout']['type'] ) && 'flex' === $block->context['layout']['type'] ) { + $classnames = "is-flex-container columns-{$block->context['layout']['columns']}"; + } + } + + $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $classnames ) ); + $content = ''; foreach ( $posts as $post ) { - $content .= ( + $block_content = ( new WP_Block( $block->parsed_block, array( @@ -71,8 +96,13 @@ function render_block_core_query_loop( $attributes, $content, $block ) { ) ) )->render( array( 'dynamic' => false ) ); + $content .= "<li>{$block_content}</li>"; } - return $content; + return sprintf( + '<ul %1$s>%2$s</ul>', + $wrapper_attributes, + $content + ); } /** diff --git a/packages/block-library/src/query-loop/style.scss b/packages/block-library/src/query-loop/style.scss new file mode 100644 index 00000000000000..676040b147ebbd --- /dev/null +++ b/packages/block-library/src/query-loop/style.scss @@ -0,0 +1,36 @@ +.wp-block-query-loop { + max-width: 100%; + list-style: none; + padding: 0; + + li { + clear: both; + } + + &.is-flex-container { + flex-direction: row; + display: flex; + flex-wrap: wrap; + + li { + margin: 0 0 1.25em 0; + width: 100%; + } + + @include break-small { + li { + margin-right: 1.25em; + } + + @for $i from 2 through 6 { + &.is-flex-container.columns-#{ $i } > li { + width: calc((100% / #{ $i }) - 1.25em + (1.25em / #{ $i })); + + &:nth-child( #{ $i }n ) { + margin-right: 0; + } + } + } + } + } +} diff --git a/packages/block-library/src/query/block.json b/packages/block-library/src/query/block.json index 02f8f60d76fd14..302ea1a8e80856 100644 --- a/packages/block-library/src/query/block.json +++ b/packages/block-library/src/query/block.json @@ -19,18 +19,28 @@ "orderBy": "date", "author": "", "search": "", - "exclude": [] + "exclude": [], + "sticky": "", + "inherit": true + } + }, + "layout": { + "type": "object", + "default": { + "type": "list" } } }, "providesContext": { "queryId": "queryId", - "query": "query" + "query": "query", + "layout": "layout" }, "usesContext": [ "postId" ], "supports": { "html": false - } + }, + "editorStyle": "wp-block-query-editor" } diff --git a/packages/block-library/src/query/edit/index.js b/packages/block-library/src/query/edit/index.js index 74807aba44809b..45be4c7f88537c 100644 --- a/packages/block-library/src/query/edit/index.js +++ b/packages/block-library/src/query/edit/index.js @@ -6,8 +6,8 @@ import { useInstanceId } from '@wordpress/compose'; import { useEffect } from '@wordpress/element'; import { BlockControls, - InnerBlocks, useBlockProps, + __experimentalUseInnerBlocksProps as useInnerBlocksProps, } from '@wordpress/block-editor'; /** @@ -16,16 +16,19 @@ import { import QueryToolbar from './query-toolbar'; import QueryProvider from './query-provider'; import QueryInspectorControls from './query-inspector-controls'; +import QueryPlaceholder from './query-placeholder'; import { DEFAULTS_POSTS_PER_PAGE } from '../constants'; const TEMPLATE = [ [ 'core/query-loop' ] ]; -export default function QueryEdit( { - attributes: { queryId, query }, +export function QueryContent( { + attributes, context: { postId }, setAttributes, } ) { - const instanceId = useInstanceId( QueryEdit ); + const { queryId, query, layout } = attributes; + const instanceId = useInstanceId( QueryContent ); const blockProps = useBlockProps(); + const innerBlocksProps = useInnerBlocksProps( {}, { template: TEMPLATE } ); const { postsPerPage } = useSelect( ( select ) => { const { getSettings } = select( 'core/block-editor' ); return { @@ -44,7 +47,7 @@ export default function QueryEdit( { if ( ! query.perPage && postsPerPage ) { newQuery.perPage = postsPerPage; } - if ( Object.keys( newQuery ).length ) { + if ( !! Object.keys( newQuery ).length ) { updateQuery( newQuery ); } }, [ query.perPage, query.exclude, postId ] ); @@ -57,22 +60,42 @@ export default function QueryEdit( { }, [ queryId, instanceId ] ); const updateQuery = ( newQuery ) => setAttributes( { query: { ...query, ...newQuery } } ); + const updateLayout = ( newLayout ) => + setAttributes( { layout: { ...layout, ...newLayout } } ); return ( <> - <QueryInspectorControls query={ query } setQuery={ updateQuery } /> + <QueryInspectorControls + attributes={ attributes } + setQuery={ updateQuery } + setLayout={ updateLayout } + /> <BlockControls> - <QueryToolbar query={ query } setQuery={ updateQuery } /> + <QueryToolbar + attributes={ attributes } + setQuery={ updateQuery } + setLayout={ updateLayout } + /> </BlockControls> <div { ...blockProps }> <QueryProvider> - <InnerBlocks - template={ TEMPLATE } - templateInsertUpdatesSelection={ false } - /> + <div { ...innerBlocksProps } /> </QueryProvider> </div> </> ); } +const QueryEdit = ( props ) => { + const { clientId } = props; + const hasInnerBlocks = useSelect( + ( select ) => + !! select( 'core/block-editor' ).getBlocks( clientId ).length, + [ clientId ] + ); + const Component = hasInnerBlocks ? QueryContent : QueryPlaceholder; + + return <Component { ...props } />; +}; + +export default QueryEdit; export * from './query-provider'; diff --git a/packages/block-library/src/query/edit/query-inspector-controls.js b/packages/block-library/src/query/edit/query-inspector-controls.js index fda77e91000d40..93323ae63a38cd 100644 --- a/packages/block-library/src/query/edit/query-inspector-controls.js +++ b/packages/block-library/src/query/edit/query-inspector-controls.js @@ -13,11 +13,21 @@ import { TextControl, FormTokenField, SelectControl, + RangeControl, + ToggleControl, + Notice, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { InspectorControls } from '@wordpress/block-editor'; import { useSelect } from '@wordpress/data'; -import { useEffect, useState, useCallback, useMemo } from '@wordpress/element'; +import { addQueryArgs } from '@wordpress/url'; +import { + useEffect, + useState, + useCallback, + useMemo, + createInterpolateElement, +} from '@wordpress/element'; /** * Internal dependencies @@ -25,10 +35,43 @@ import { useEffect, useState, useCallback, useMemo } from '@wordpress/element'; import { getTermsInfo } from '../utils'; import { MAX_FETCHED_TERMS } from '../constants'; -export default function QueryInspectorControls( { query, setQuery } ) { - const { order, orderBy, author: selectedAuthorId, postType } = query; +const stickyOptions = [ + { label: __( 'Include' ), value: '' }, + { label: __( 'Exclude' ), value: 'exclude' }, + { label: __( 'Only' ), value: 'only' }, +]; + +const CreateNewPostLink = ( { type } ) => { + const newPostUrl = addQueryArgs( 'post-new.php', { + post_type: type, + } ); + return ( + <div className="wp-block-query__create-new-link"> + { createInterpolateElement( + __( '<a>Create a new post</a> for this feed.' ), + // eslint-disable-next-line jsx-a11y/anchor-has-content + { a: <a href={ newPostUrl } /> } + ) } + </div> + ); +}; + +export default function QueryInspectorControls( { + attributes: { query, layout }, + setQuery, + setLayout, +} ) { + const { + order, + orderBy, + author: selectedAuthorId, + postType, + sticky, + inherit, + } = query; const [ showCategories, setShowCategories ] = useState( true ); const [ showTags, setShowTags ] = useState( true ); + const [ showSticky, setShowSticky ] = useState( postType === 'post' ); const { authorList, categories, tags, postTypes } = useSelect( ( select ) => { const { getEntityRecords, getPostTypes } = select( 'core' ); @@ -44,7 +87,7 @@ export default function QueryInspectorControls( { query, setQuery } ) { termsQuery ); const excludedPostTypes = [ 'attachment' ]; - const filteredPostTypes = getPostTypes()?.filter( + const filteredPostTypes = getPostTypes( { per_page: -1 } )?.filter( ( { viewable, slug } ) => viewable && ! excludedPostTypes.includes( slug ) ); @@ -72,6 +115,9 @@ export default function QueryInspectorControls( { query, setQuery } ) { setShowCategories( postTypeTaxonomies.includes( 'category' ) ); setShowTags( postTypeTaxonomies.includes( 'post_tag' ) ); }, [ postType, postTypesTaxonomiesMap ] ); + useEffect( () => { + setShowSticky( postType === 'post' ); + }, [ postType ] ); const postTypesSelectOptions = useMemo( () => ( postTypes || [] ).map( ( { labels, slug } ) => ( { @@ -88,6 +134,9 @@ export default function QueryInspectorControls( { query, setQuery } ) { if ( ! postTypesTaxonomiesMap[ newValue ].includes( 'post_tag' ) ) { updateQuery.tagIds = []; } + if ( newValue !== 'post' ) { + updateQuery.sticky = ''; + } setQuery( updateQuery ); }; // Handles categories and tags changes. @@ -104,64 +153,121 @@ export default function QueryInspectorControls( { query, setQuery } ) { const [ querySearch, setQuerySearch ] = useState( query.search ); const onChangeDebounced = useCallback( - debounce( () => setQuery( { search: querySearch } ), 250 ), - [ querySearch ] + debounce( () => { + if ( query.search !== querySearch ) { + setQuery( { search: querySearch } ); + } + }, 250 ), + [ querySearch, query.search ] ); + useEffect( () => { onChangeDebounced(); return onChangeDebounced.cancel; }, [ querySearch, onChangeDebounced ] ); + return ( <InspectorControls> - <PanelBody title={ __( 'Filtering and Sorting' ) }> - <SelectControl - options={ postTypesSelectOptions } - value={ postType } - label={ __( 'Post Type' ) } - onChange={ onPostTypeChange } + <CreateNewPostLink type={ postType } /> + <PanelBody title={ __( 'Settings' ) }> + <ToggleControl + label={ __( 'Inherit query from URL' ) } + help={ __( + 'Disable the option to customize the query arguments. Leave enabled to inherit the global query depending on the URL.' + ) } + checked={ !! inherit } + onChange={ ( value ) => setQuery( { inherit: !! value } ) } /> - { showCategories && categories?.terms?.length > 0 && ( - <FormTokenField - label={ __( 'Categories' ) } - value={ ( query.categoryIds || [] ).map( - ( categoryId ) => ( { - id: categoryId, - value: categories.mapById[ categoryId ].name, - } ) + { ! inherit && ( + <SelectControl + options={ postTypesSelectOptions } + value={ postType } + label={ __( 'Post Type' ) } + onChange={ onPostTypeChange } + /> + ) } + { layout?.type === 'flex' && ( + <> + <RangeControl + label={ __( 'Columns' ) } + value={ layout.columns } + onChange={ ( value ) => + setLayout( { columns: value } ) + } + min={ 2 } + max={ Math.max( 6, layout.columns ) } + /> + { layout.columns > 6 && ( + <Notice status="warning" isDismissible={ false }> + { __( + 'This column count exceeds the recommended amount and may cause visual breakage.' + ) } + </Notice> ) } - suggestions={ categories.names } - onChange={ onCategoriesChange } + </> + ) } + { ! inherit && ( + <QueryControls + { ...{ order, orderBy } } + onOrderChange={ ( value ) => + setQuery( { order: value } ) + } + onOrderByChange={ ( value ) => + setQuery( { orderBy: value } ) + } /> ) } - { showTags && tags?.terms?.length > 0 && ( - <FormTokenField - label={ __( 'Tags' ) } - value={ ( query.tagIds || [] ).map( ( tagId ) => ( { - id: tagId, - value: tags.mapById[ tagId ].name, - } ) ) } - suggestions={ tags.names } - onChange={ onTagsChange } + { showSticky && ( + <SelectControl + label={ __( 'Sticky posts' ) } + options={ stickyOptions } + value={ sticky } + onChange={ ( value ) => setQuery( { sticky: value } ) } /> ) } - <QueryControls - { ...{ order, orderBy, selectedAuthorId, authorList } } - onOrderChange={ ( value ) => setQuery( { order: value } ) } - onOrderByChange={ ( value ) => - setQuery( { orderBy: value } ) - } - onAuthorChange={ ( value ) => - setQuery( { - author: value !== '' ? +value : undefined, - } ) - } - /> - <TextControl - label={ __( 'Search' ) } - value={ querySearch } - onChange={ setQuerySearch } - /> </PanelBody> + { ! inherit && ( + <PanelBody title={ __( 'Filters' ) }> + { showCategories && categories?.terms?.length > 0 && ( + <FormTokenField + label={ __( 'Categories' ) } + value={ ( query.categoryIds || [] ).map( + ( categoryId ) => ( { + id: categoryId, + value: + categories.mapById[ categoryId ].name, + } ) + ) } + suggestions={ categories.names } + onChange={ onCategoriesChange } + /> + ) } + { showTags && tags?.terms?.length > 0 && ( + <FormTokenField + label={ __( 'Tags' ) } + value={ ( query.tagIds || [] ).map( ( tagId ) => ( { + id: tagId, + value: tags.mapById[ tagId ].name, + } ) ) } + suggestions={ tags.names } + onChange={ onTagsChange } + /> + ) } + <QueryControls + { ...{ selectedAuthorId, authorList } } + onAuthorChange={ ( value ) => + setQuery( { + author: value !== '' ? +value : undefined, + } ) + } + /> + <TextControl + label={ __( 'Keyword' ) } + value={ querySearch } + onChange={ setQuerySearch } + /> + </PanelBody> + ) } </InspectorControls> ); } diff --git a/packages/block-library/src/query/edit/query-placeholder.js b/packages/block-library/src/query/edit/query-placeholder.js new file mode 100644 index 00000000000000..6d9172141a5005 --- /dev/null +++ b/packages/block-library/src/query/edit/query-placeholder.js @@ -0,0 +1,68 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { + useBlockProps, + __experimentalBlockVariationPicker, + __experimentalGetMatchingVariation as getMatchingVariation, +} from '@wordpress/block-editor'; +import { + createBlocksFromInnerBlocksTemplate, + store as blocksStore, +} from '@wordpress/blocks'; + +const QueryPlaceholder = ( { clientId, name, attributes, setAttributes } ) => { + const { + blockType, + defaultVariation, + scopeVariations, + allVariations, + } = useSelect( + ( select ) => { + const { + getBlockVariations, + getBlockType, + getDefaultBlockVariation, + } = select( blocksStore ); + + return { + blockType: getBlockType( name ), + defaultVariation: getDefaultBlockVariation( name, 'block' ), + scopeVariations: getBlockVariations( name, 'block' ), + allVariations: getBlockVariations( name ), + }; + }, + [ name ] + ); + const { replaceInnerBlocks } = useDispatch( 'core/block-editor' ); + const blockProps = useBlockProps(); + const matchingVariation = getMatchingVariation( attributes, allVariations ); + const icon = matchingVariation?.icon || blockType?.icon?.src; + const label = matchingVariation?.title || blockType?.title; + return ( + <div { ...blockProps }> + <__experimentalBlockVariationPicker + icon={ icon } + label={ label } + variations={ scopeVariations } + onSelect={ ( nextVariation = defaultVariation ) => { + if ( nextVariation.attributes ) { + setAttributes( nextVariation.attributes ); + } + if ( nextVariation.innerBlocks ) { + replaceInnerBlocks( + clientId, + createBlocksFromInnerBlocksTemplate( + nextVariation.innerBlocks + ), + false + ); + } + } } + /> + </div> + ); +}; + +export default QueryPlaceholder; diff --git a/packages/block-library/src/query/edit/query-toolbar.js b/packages/block-library/src/query/edit/query-toolbar.js index 8d519befd697c1..dd079aa1a81103 100644 --- a/packages/block-library/src/query/edit/query-toolbar.js +++ b/packages/block-library/src/query/edit/query-toolbar.js @@ -10,66 +10,88 @@ import { __experimentalNumberControl as NumberControl, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { postList } from '@wordpress/icons'; +import { settings, list, grid } from '@wordpress/icons'; -export default function QueryToolbar( { query, setQuery } ) { +export default function QueryToolbar( { + attributes: { query, layout }, + setQuery, + setLayout, +} ) { + const layoutControls = [ + { + icon: list, + title: __( 'List view' ), + onClick: () => setLayout( { type: 'list' } ), + isActive: layout?.type === 'list', + }, + { + icon: grid, + title: __( 'Grid view' ), + onClick: () => + setLayout( { type: 'flex', columns: layout?.columns || 3 } ), + isActive: layout?.type === 'flex', + }, + ]; return ( - <ToolbarGroup> - <Dropdown - contentClassName="block-library-query-toolbar__popover" - renderToggle={ ( { onToggle } ) => ( - <ToolbarButton - icon={ postList } - label={ __( 'Query' ) } - onClick={ onToggle } - /> - ) } - renderContent={ () => ( - <> - <BaseControl> - <NumberControl - __unstableInputWidth="60px" - label={ __( 'Items per Page' ) } - labelPosition="edge" - min={ 1 } - max={ 100 } - onChange={ ( value ) => - setQuery( { perPage: +value ?? -1 } ) - } - step="1" - value={ query.perPage } - isDragEnabled={ false } - /> - </BaseControl> - <BaseControl> - <NumberControl - __unstableInputWidth="60px" - label={ __( 'Offset' ) } - labelPosition="edge" - min={ 0 } - max={ 100 } - onChange={ ( value ) => - setQuery( { offset: +value } ) - } - step="1" - value={ query.offset } - isDragEnabled={ false } - /> - </BaseControl> - <BaseControl> - <RangeControl - label={ __( 'Number of Pages' ) } - min={ 1 } - allowReset - value={ query.pages } - onChange={ ( value ) => - setQuery( { pages: value ?? -1 } ) - } - /> - </BaseControl> - </> - ) } - /> - </ToolbarGroup> + <> + <ToolbarGroup> + <Dropdown + contentClassName="block-library-query-toolbar__popover" + renderToggle={ ( { onToggle } ) => ( + <ToolbarButton + icon={ settings } + label={ __( 'Display settings' ) } + onClick={ onToggle } + /> + ) } + renderContent={ () => ( + <> + <BaseControl> + <NumberControl + __unstableInputWidth="60px" + label={ __( 'Items per Page' ) } + labelPosition="edge" + min={ 1 } + max={ 100 } + onChange={ ( value ) => + setQuery( { perPage: +value ?? -1 } ) + } + step="1" + value={ query.perPage } + isDragEnabled={ false } + /> + </BaseControl> + <BaseControl> + <NumberControl + __unstableInputWidth="60px" + label={ __( 'Offset' ) } + labelPosition="edge" + min={ 0 } + max={ 100 } + onChange={ ( value ) => + setQuery( { offset: +value } ) + } + step="1" + value={ query.offset } + isDragEnabled={ false } + /> + </BaseControl> + <BaseControl> + <RangeControl + label={ __( 'Number of Pages' ) } + min={ 1 } + allowReset + value={ query.pages } + onChange={ ( value ) => + setQuery( { pages: value ?? -1 } ) + } + /> + </BaseControl> + </> + ) } + /> + </ToolbarGroup> + <ToolbarGroup controls={ layoutControls } /> + </> ); } diff --git a/packages/block-library/src/query/editor.scss b/packages/block-library/src/query/editor.scss index 90f58ba63a2f15..e28e6de5643246 100644 --- a/packages/block-library/src/query/editor.scss +++ b/packages/block-library/src/query/editor.scss @@ -1,3 +1,11 @@ .editor-styles-wrapper .wp-block.wp-block-query { max-width: 100%; } + +.block-library-query-toolbar__popover .components-popover__content { + min-width: 230px; +} + +.wp-block-query__create-new-link { + padding: 0 $grid-unit-20 $grid-unit-20 56px; +} diff --git a/packages/block-library/src/query/icons.js b/packages/block-library/src/query/icons.js new file mode 100644 index 00000000000000..d182c0f36e6255 --- /dev/null +++ b/packages/block-library/src/query/icons.js @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; + +export const titleDate = ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"> + <Path d="M41 9H7v3h34V9zm-22 5H7v1h12v-1zM7 26h12v1H7v-1zm34-5H7v3h34v-3zM7 38h12v1H7v-1zm34-5H7v3h34v-3z" /> + </SVG> +); + +export const titleExcerpt = ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"> + <Path d="M41 9H7v3h34V9zm-4 5H7v1h30v-1zm4 3H7v1h34v-1zM7 20h30v1H7v-1zm0 12h30v1H7v-1zm34 3H7v1h34v-1zM7 38h30v1H7v-1zm34-11H7v3h34v-3z" /> + </SVG> +); + +export const titleDateExcerpt = ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"> + <Path d="M41 9H7v3h34V9zm-22 5H7v1h12v-1zm22 3H7v1h34v-1zM7 20h34v1H7v-1zm0 12h12v1H7v-1zm34 3H7v1h34v-1zM7 38h34v1H7v-1zm34-11H7v3h34v-3z" /> + </SVG> +); + +export const imageDateTitle = ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"> + <Path d="M7 9h34v6H7V9zm12 8H7v1h12v-1zm18 3H7v1h30v-1zm0 18H7v1h30v-1zM7 35h12v1H7v-1zm34-8H7v6h34v-6z" /> + </SVG> +); diff --git a/packages/block-library/src/query/index.js b/packages/block-library/src/query/index.js index 21fe40c9e330e5..8de7d4e966b24b 100644 --- a/packages/block-library/src/query/index.js +++ b/packages/block-library/src/query/index.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { loop as icon } from '@wordpress/icons'; /** * Internal dependencies @@ -9,14 +10,18 @@ import { __ } from '@wordpress/i18n'; import metadata from './block.json'; import edit from './edit'; import save from './save'; +import variations from './variations'; const { name } = metadata; export { metadata, name }; export const settings = { title: __( 'Query' ), + icon, + description: __( 'Displays a list of posts as a result of a query.' ), edit, save, + variations, }; export { useQueryContext } from './edit'; diff --git a/packages/block-library/src/query/test/utils.js b/packages/block-library/src/query/test/utils.js index c7acb4b6110bae..9c92ae7c581714 100644 --- a/packages/block-library/src/query/test/utils.js +++ b/packages/block-library/src/query/test/utils.js @@ -13,8 +13,8 @@ describe( 'Query block utils', () => { expect( getTermsInfo( terms ) ).toEqual( expect.objectContaining( { mapById: expect.objectContaining( { - '4': expect.objectContaining( { name: 'nba' } ), - '11': expect.objectContaining( { + 4: expect.objectContaining( { name: 'nba' } ), + 11: expect.objectContaining( { name: 'featured', } ), } ), diff --git a/packages/block-library/src/query/variations.js b/packages/block-library/src/query/variations.js new file mode 100644 index 00000000000000..dbf9290cc58fc1 --- /dev/null +++ b/packages/block-library/src/query/variations.js @@ -0,0 +1,104 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { postList } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import { + titleDate, + titleExcerpt, + titleDateExcerpt, + imageDateTitle, +} from './icons'; + +const variations = [ + { + name: 'posts-list', + title: __( 'Posts List' ), + description: __( + 'Display a list of your most recent posts, excluding sticky posts.' + ), + icon: postList, + attributes: { + query: { + perPage: 4, + pages: 1, + offset: 0, + postType: 'post', + categoryIds: [], + tagIds: [], + order: 'desc', + orderBy: 'date', + author: '', + search: '', + sticky: 'exclude', + }, + }, + scope: [ 'inserter' ], + }, + { + name: 'title-date', + title: __( 'Title & Date' ), + icon: titleDate, + innerBlocks: [ + [ + 'core/query-loop', + {}, + [ [ 'core/post-title' ], [ 'core/post-date' ] ], + ], + ], + scope: [ 'block' ], + }, + { + name: 'title-excerpt', + title: __( 'Title & Excerpt' ), + icon: titleExcerpt, + innerBlocks: [ + [ + 'core/query-loop', + {}, + [ [ 'core/post-title' ], [ 'core/post-excerpt' ] ], + ], + ], + scope: [ 'block' ], + }, + { + name: 'title-date-excerpt', + title: __( 'Title, Date, & Excerpt' ), + icon: titleDateExcerpt, + innerBlocks: [ + [ + 'core/query-loop', + {}, + [ + [ 'core/post-title' ], + [ 'core/post-date' ], + [ 'core/post-excerpt' ], + ], + ], + ], + scope: [ 'block' ], + }, + { + name: 'image-date-title', + title: __( 'Image, Date, & Title' ), + icon: imageDateTitle, + innerBlocks: [ + [ + 'core/query-loop', + {}, + [ + [ 'core/post-featured-image' ], + [ 'core/post-date' ], + [ 'core/post-title' ], + ], + ], + ], + scope: [ 'block' ], + }, +]; + +export default variations; diff --git a/packages/block-library/src/quote/block.json b/packages/block-library/src/quote/block.json index 9de3a338c9f1ed..bba83461367fa1 100644 --- a/packages/block-library/src/quote/block.json +++ b/packages/block-library/src/quote/block.json @@ -22,5 +22,7 @@ }, "supports": { "anchor": true - } + }, + "editorStyle": "wp-block-quote-editor", + "style": "wp-block-quote" } diff --git a/packages/block-library/src/quote/edit.js b/packages/block-library/src/quote/edit.js index 0647e91d1b4895..8064aca0eef303 100644 --- a/packages/block-library/src/quote/edit.js +++ b/packages/block-library/src/quote/edit.js @@ -24,12 +24,14 @@ export default function QuoteEdit( { onReplace, className, insertBlocksAfter, + mergedStyle, } ) { const { align, value, citation } = attributes; const blockProps = useBlockProps( { className: classnames( className, { [ `has-text-align-${ align }` ]: align, } ), + style: mergedStyle, } ); return ( @@ -60,6 +62,7 @@ export default function QuoteEdit( { onReplace( [] ); } } } + aria-label={ __( 'Quote text' ) } placeholder={ // translators: placeholder text used for the quote __( 'Write quote…' ) @@ -86,6 +89,7 @@ export default function QuoteEdit( { } ) } __unstableMobileNoFocusOnMount + aria-label={ __( 'Quote citation text' ) } placeholder={ // translators: placeholder text used for the citation __( 'Write citation…' ) diff --git a/packages/block-library/src/quote/theme.scss b/packages/block-library/src/quote/theme.scss index b801f8c687f0d6..a980bbb373279d 100644 --- a/packages/block-library/src/quote/theme.scss +++ b/packages/block-library/src/quote/theme.scss @@ -1,12 +1,12 @@ .wp-block-quote { - border-left: 0.25em solid $black; + border-left: 0.25em solid currentColor; margin: 0 0 1.75em 0; padding-left: 1em; cite, footer, &__citation { - color: #555; + color: currentColor; font-size: 0.8125em; margin-top: 1em; position: relative; @@ -15,7 +15,7 @@ &.has-text-align-right { border-left: none; - border-right: 0.25em solid $black; + border-right: 0.25em solid currentColor; padding-left: 0; padding-right: 1em; } diff --git a/packages/block-library/src/rss/block.json b/packages/block-library/src/rss/block.json index 49555c41cb0ea6..15a0feaa6f3552 100644 --- a/packages/block-library/src/rss/block.json +++ b/packages/block-library/src/rss/block.json @@ -39,5 +39,7 @@ "supports": { "align": true, "html": false - } + }, + "editorStyle": "wp-block-rss-editor", + "style": "wp-block-rss" } diff --git a/packages/block-library/src/rss/editor.scss b/packages/block-library/src/rss/editor.scss index 7b08c85d3b55f8..48d14bc25631c5 100644 --- a/packages/block-library/src/rss/editor.scss +++ b/packages/block-library/src/rss/editor.scss @@ -1,4 +1,4 @@ -.block-editor .wp-block-rss { +.wp-block-rss { padding-left: 2.5em; &.is-grid { padding-left: 0; diff --git a/packages/block-library/src/search/block.json b/packages/block-library/src/search/block.json index d57ba7fd9c3900..534be0e97cb5fa 100644 --- a/packages/block-library/src/search/block.json +++ b/packages/block-library/src/search/block.json @@ -35,5 +35,7 @@ "supports": { "align": [ "left", "center", "right" ], "html": false - } + }, + "editorStyle": "wp-block-search-editor", + "style": "wp-block-search" } diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js index 7e7b1934b3fc31..c82086f2e5ff33 100644 --- a/packages/block-library/src/search/edit.js +++ b/packages/block-library/src/search/edit.js @@ -221,17 +221,6 @@ export default function SearchEdit( { > { __( 'Button Inside' ) } </MenuItem> - <MenuItem - icon={ buttonOnly } - onClick={ () => { - setAttributes( { - buttonPosition: 'button-only', - } ); - onClose(); - } } - > - { __( 'Button Only' ) } - </MenuItem> </MenuGroup> ) } </DropdownMenu> @@ -343,7 +332,6 @@ export default function SearchEdit( { width: `${ width }${ widthUnit }`, } } className="wp-block-search__inside-wrapper" - isResetValueOnUnitChange minWidth={ MIN_WIDTH } enable={ getResizableSides() } onResizeStart={ ( event, direction, elt ) => { diff --git a/packages/block-library/src/search/editor.scss b/packages/block-library/src/search/editor.scss index 96a60f75569c57..8f1bc15c57a6eb 100644 --- a/packages/block-library/src/search/editor.scss +++ b/packages/block-library/src/search/editor.scss @@ -7,31 +7,17 @@ padding: $grid-unit-05; } - .wp-block-search__input, - &.wp-block-search__button-inside .wp-block-search__inside-wrapper { - border-radius: $radius-block-ui; - border: 1px solid $gray-600; - color: $dark-gray-placeholder; - font-family: $default-font; - font-size: $default-font-size; - background-color: $white; + .wp-block-search__button { + height: auto; + border-radius: initial; - &:focus { - outline: none; + // This needs high specificity because it otherwise inherits styles from `components-button`. + // stylelint-disable-line no-duplicate-selectors + &.wp-block-search__button.wp-block-search__button { + padding: 6px 10px; } } - .wp-block-search__button { - background: #f7f7f7; - border-radius: $radius-block-ui; - border: 1px solid #ccc; - box-shadow: inset 0 -1px 0 #ccc; - font-family: $default-font; - font-size: $default-font-size; - padding: 6px 10px; - color: #32373c; - } - &__components-button-group { margin-top: 10px; } diff --git a/packages/block-library/src/search/icons.js b/packages/block-library/src/search/icons.js index d795398382859b..3d50d161462965 100644 --- a/packages/block-library/src/search/icons.js +++ b/packages/block-library/src/search/icons.js @@ -18,7 +18,7 @@ export const buttonOutside = ( height="9.5" transform="rotate(-90 4.75 15.25)" stroke="currentColor" - stroke-width="1.5" + strokeWidth="1.5" fill="none" /> <Rect x="16" y="10" width="4" height="4" rx="1" fill="currentColor" /> @@ -34,7 +34,7 @@ export const buttonInside = ( height="14.5" transform="rotate(-90 4.75 15.25)" stroke="currentColor" - stroke-width="1.5" + strokeWidth="1.5" fill="none" /> <Rect x="14" y="10" width="4" height="4" rx="1" fill="currentColor" /> @@ -51,7 +51,7 @@ export const noButton = ( transform="rotate(-90 4.75 15.25)" stroke="currentColor" fill="none" - stroke-width="1.5" + strokeWidth="1.5" /> </SVG> ); @@ -66,7 +66,7 @@ export const buttonWithIcon = ( rx="1.25" stroke="currentColor" fill="none" - stroke-width="1.5" + strokeWidth="1.5" /> <Rect x="8" y="11" width="8" height="2" fill="currentColor" /> </SVG> @@ -82,7 +82,7 @@ export const toggleLabel = ( transform="rotate(-90 4.75 17.25)" stroke="currentColor" fill="none" - stroke-width="1.5" + strokeWidth="1.5" /> <Rect x="4" y="7" width="10" height="2" fill="currentColor" /> </SVG> diff --git a/packages/block-library/src/search/style.scss b/packages/block-library/src/search/style.scss index 5d38578aa14460..6f47aa3761dbcb 100644 --- a/packages/block-library/src/search/style.scss +++ b/packages/block-library/src/search/style.scss @@ -1,4 +1,23 @@ .wp-block-search { + + .wp-block-search__button { + background: #f7f7f7; + border: 1px solid #ccc; + padding: 0.375em 0.625em; + color: #32373c; + margin-left: 0.625em; + word-break: normal; + + &.has-icon { + line-height: 0; + } + + svg { + min-width: 1.5em; + min-height: 1.5em; + } + } + .wp-block-search__inside-wrapper { display: flex; flex: auto; @@ -16,16 +35,6 @@ border: 1px solid $gray-600; } - .wp-block-search__button { - margin-left: 0.625em; - word-break: normal; - - svg { - min-width: 1.5em; - min-height: 1.5em; - } - } - &.wp-block-search__button-only { .wp-block-search__button { margin-left: 0; diff --git a/packages/block-library/src/separator/block.json b/packages/block-library/src/separator/block.json index 3cf6ae1b505ee1..2983a93a86190a 100644 --- a/packages/block-library/src/separator/block.json +++ b/packages/block-library/src/separator/block.json @@ -13,5 +13,7 @@ "supports": { "anchor": true, "align": ["center","wide","full"] - } + }, + "editorStyle": "wp-block-separator-editor", + "style": "wp-block-separator" } diff --git a/packages/block-library/src/separator/editor.scss b/packages/block-library/src/separator/editor.scss index af8aa829049770..24e940684279e8 100644 --- a/packages/block-library/src/separator/editor.scss +++ b/packages/block-library/src/separator/editor.scss @@ -2,9 +2,4 @@ // Prevent margin collapsing so the area to select the separator is bigger. padding-top: 0.1px; padding-bottom: 0.1px; - - &.block-editor-block-list__block { // Needs specificity. - margin-top: 0; - margin-bottom: 0; - } } diff --git a/packages/block-library/src/shortcode/block.json b/packages/block-library/src/shortcode/block.json index 8c419763739044..4f92abd419f6e9 100644 --- a/packages/block-library/src/shortcode/block.json +++ b/packages/block-library/src/shortcode/block.json @@ -12,5 +12,6 @@ "className": false, "customClassName": false, "html": false - } + }, + "editorStyle": "wp-block-shortcode-editor" } diff --git a/packages/block-library/src/shortcode/edit.js b/packages/block-library/src/shortcode/edit.js index 02de1744787642..aecebd115a983f 100644 --- a/packages/block-library/src/shortcode/edit.js +++ b/packages/block-library/src/shortcode/edit.js @@ -23,6 +23,7 @@ export default function ShortcodeEdit( { attributes, setAttributes } ) { className="blocks-shortcode__textarea" id={ inputId } value={ attributes.text } + aria-label={ __( 'Shortcode text' ) } placeholder={ __( 'Write shortcode here…' ) } onChange={ ( text ) => setAttributes( { text } ) } /> diff --git a/packages/block-library/src/shortcode/editor.scss b/packages/block-library/src/shortcode/editor.scss index ade8279e6d6101..da4a806374e678 100644 --- a/packages/block-library/src/shortcode/editor.scss +++ b/packages/block-library/src/shortcode/editor.scss @@ -1,29 +1,13 @@ -.wp-block-shortcode { - display: flex; - flex-direction: column; - padding: $block-padding; - font-size: $default-font-size; - font-family: $default-font; - margin-bottom: $default-block-margin; - - label { - display: flex; - align-items: center; - white-space: nowrap; - font-weight: 600; - flex-shrink: 0; - } - +[data-type="core/shortcode"] { .block-editor-plain-text { max-height: 250px; } - .dashicon { - margin-right: $grid-unit-10; + &.components-placeholder { + min-height: 0; } } -.block-editor .blocks-shortcode__textarea, .blocks-shortcode__textarea { @include input-control; } diff --git a/packages/block-library/src/site-logo/block.json b/packages/block-library/src/site-logo/block.json index b2818f5e751ac6..35313f0212b39d 100644 --- a/packages/block-library/src/site-logo/block.json +++ b/packages/block-library/src/site-logo/block.json @@ -12,5 +12,7 @@ }, "supports": { "html": false - } + }, + "editorStyle": "wp-block-site-logo-editor", + "style": "wp-block-site-logo" } diff --git a/packages/block-library/src/site-logo/index.js b/packages/block-library/src/site-logo/index.js index b021f2b08bbecc..07e7856860fc3f 100644 --- a/packages/block-library/src/site-logo/index.js +++ b/packages/block-library/src/site-logo/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; /** * Internal dependencies @@ -17,6 +17,14 @@ export const settings = { title: __( 'Site Logo' ), description: __( 'Show a site logo' ), icon, + styles: [ + { + name: 'default', + label: _x( 'Default', 'block style' ), + isDefault: true, + }, + { name: 'rounded', label: _x( 'Rounded', 'block style' ) }, + ], supports: { align: true, alignWide: false, diff --git a/packages/block-library/src/site-logo/index.php b/packages/block-library/src/site-logo/index.php index efadd3ce16c61f..3b24c721a854ba 100644 --- a/packages/block-library/src/site-logo/index.php +++ b/packages/block-library/src/site-logo/index.php @@ -43,16 +43,14 @@ function render_block_core_site_logo( $attributes ) { * Registers the `core/site-logo` block on the server. */ function register_block_core_site_logo() { - if ( gutenberg_is_experiment_enabled( 'gutenberg-full-site-editing' ) ) { - register_block_type_from_metadata( - __DIR__ . '/site-logo', - array( - 'render_callback' => 'render_block_core_site_logo', - ) - ); - add_filter( 'pre_set_theme_mod_custom_logo', 'sync_site_logo_to_theme_mod' ); - add_filter( 'theme_mod_custom_logo', 'override_custom_logo_theme_mod' ); - } + register_block_type_from_metadata( + __DIR__ . '/site-logo', + array( + 'render_callback' => 'render_block_core_site_logo', + ) + ); + add_filter( 'pre_set_theme_mod_custom_logo', 'sync_site_logo_to_theme_mod' ); + add_filter( 'theme_mod_custom_logo', 'override_custom_logo_theme_mod' ); } add_action( 'init', 'register_block_core_site_logo' ); diff --git a/packages/block-library/src/site-logo/style.scss b/packages/block-library/src/site-logo/style.scss index 6e186d32296df0..c3ad4dc2a57f22 100644 --- a/packages/block-library/src/site-logo/style.scss +++ b/packages/block-library/src/site-logo/style.scss @@ -1,5 +1,14 @@ .wp-block-custom-logo { + line-height: 0; + .aligncenter { display: table; } + + // Variations + &.is-style-rounded img { + // We use an absolute pixel to prevent the oval shape that a value of 50% would give + // to rectangular images. A pill-shape is better than otherwise. + border-radius: 9999px; + } } diff --git a/packages/block-library/src/site-tagline/block.json b/packages/block-library/src/site-tagline/block.json index 17220173c408e9..cf7ae739576b9e 100644 --- a/packages/block-library/src/site-tagline/block.json +++ b/packages/block-library/src/site-tagline/block.json @@ -13,6 +13,7 @@ "gradients": true }, "fontSize": true, - "lineHeight": true + "lineHeight": true, + "__experimentalFontFamily": true } } diff --git a/packages/block-library/src/site-tagline/edit.js b/packages/block-library/src/site-tagline/edit.js index 937291438f1bb5..faf839c0bfb657 100644 --- a/packages/block-library/src/site-tagline/edit.js +++ b/packages/block-library/src/site-tagline/edit.js @@ -41,7 +41,8 @@ export default function SiteTaglineEdit( { attributes, setAttributes } ) { <RichText allowedFormats={ [] } onChange={ setSiteTagline } - placeholder={ __( 'Site Tagline' ) } + aria-label={ __( 'Site tagline text' ) } + placeholder={ __( 'Write site tagline…' ) } tagName="p" value={ siteTagline } { ...blockProps } diff --git a/packages/block-library/src/site-title/block.json b/packages/block-library/src/site-title/block.json index bd10511fd1b3bb..6a6cf63cb4dff2 100644 --- a/packages/block-library/src/site-title/block.json +++ b/packages/block-library/src/site-title/block.json @@ -12,11 +12,13 @@ } }, "supports": { + "align": [ "wide", "full" ], "html": false, "color": { "gradients": true }, "fontSize": true, - "lineHeight": true + "lineHeight": true, + "__experimentalFontFamily": true } } diff --git a/packages/block-library/src/site-title/edit/index.js b/packages/block-library/src/site-title/edit/index.js index df4de13dc33aba..8feb6d4a4611d9 100644 --- a/packages/block-library/src/site-title/edit/index.js +++ b/packages/block-library/src/site-title/edit/index.js @@ -50,7 +50,8 @@ export default function SiteTitleEdit( { attributes, setAttributes } ) { <RichText tagName={ tagName } - placeholder={ __( 'Site Title' ) } + aria-label={ __( 'Site title text' ) } + placeholder={ __( 'Write site title…' ) } value={ title } onChange={ setTitle } allowedFormats={ [] } diff --git a/packages/block-library/src/social-link/block.json b/packages/block-library/src/social-link/block.json index 0b9072548b6f80..0ec91e23d905d7 100644 --- a/packages/block-library/src/social-link/block.json +++ b/packages/block-library/src/social-link/block.json @@ -22,5 +22,6 @@ "supports": { "reusable": false, "html": false - } + }, + "editorStyle": "wp-block-social-link-editor" } diff --git a/packages/block-library/src/social-link/edit.native.js b/packages/block-library/src/social-link/edit.native.js index b1fe5814c1f569..7443c57f9fc95b 100644 --- a/packages/block-library/src/social-link/edit.native.js +++ b/packages/block-library/src/social-link/edit.native.js @@ -11,7 +11,7 @@ import { useEffect, useState, useRef } from '@wordpress/element'; import { ToolbarGroup, ToolbarButton, - LinkSettings, + LinkSettingsNavigation, } from '@wordpress/components'; import { compose, usePreferredColorSchemeStyle } from '@wordpress/compose'; import { __, sprintf } from '@wordpress/i18n'; @@ -162,7 +162,7 @@ const SocialLinkEdit = ( { </ToolbarGroup> </BlockControls> ) } - <LinkSettings + <LinkSettingsNavigation isVisible={ isLinkSheetVisible } attributes={ attributes } onEmptyURL={ onEmptyURL } diff --git a/packages/block-library/src/social-link/editor.scss b/packages/block-library/src/social-link/editor.scss index e938b8d74ee749..1d9bde20f72436 100644 --- a/packages/block-library/src/social-link/editor.scss +++ b/packages/block-library/src/social-link/editor.scss @@ -1,9 +1,20 @@ -.wp-block-social-links .wp-social-link button { - color: currentColor; - padding: 6px; +// The editor uses the button component, the frontend uses a link. +// Therefore we unstyle the button component to make it more like the frontend. +.wp-block-social-links .wp-social-link { + line-height: 0; + + button { + font-size: inherit; + color: currentColor; + height: auto; + line-height: 0; + + // This rule is duplicated from the style.scss and needs to be the same as there. + padding: 0.25em; + } } .wp-block-social-links.is-style-pill-shape .wp-social-link button { - padding-left: 16px; - padding-right: 16px; + padding-left: calc((2/3) * 1em); + padding-right: calc((2/3) * 1em); } diff --git a/packages/block-library/src/social-link/icons/index.js b/packages/block-library/src/social-link/icons/index.js index a57b6f3d048f75..a9b096a4b0eaab 100644 --- a/packages/block-library/src/social-link/icons/index.js +++ b/packages/block-library/src/social-link/icons/index.js @@ -22,6 +22,7 @@ export * from './mail'; export * from './mastodon'; export * from './meetup'; export * from './medium'; +export * from './patreon'; export * from './pinterest'; export * from './pocket'; export * from './reddit'; @@ -29,6 +30,8 @@ export * from './skype'; export * from './snapchat'; export * from './soundcloud'; export * from './spotify'; +export * from './telegram'; +export * from './tiktok'; export * from './tumblr'; export * from './twitch'; export * from './twitter'; diff --git a/packages/block-library/src/social-link/icons/patreon.js b/packages/block-library/src/social-link/icons/patreon.js new file mode 100644 index 00000000000000..9970d49eb4e0f6 --- /dev/null +++ b/packages/block-library/src/social-link/icons/patreon.js @@ -0,0 +1,11 @@ +/** + * WordPress dependencies + */ +import { Circle, Rect, SVG } from '@wordpress/primitives'; + +export const PatreonIcon = () => ( + <SVG width="24" height="24" viewBox="0 0 569 546" version="1.1"> + <Circle cx="363" cy="205" r="205" /> + <Rect width="100" height="546" x="0" y="0" /> + </SVG> +); diff --git a/packages/block-library/src/social-link/icons/telegram.js b/packages/block-library/src/social-link/icons/telegram.js new file mode 100644 index 00000000000000..4a694d1d278dee --- /dev/null +++ b/packages/block-library/src/social-link/icons/telegram.js @@ -0,0 +1,10 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/primitives'; + +export const TelegramIcon = () => ( + <SVG width="24" height="24" viewBox="0 0 128 128" version="1.1"> + <Path d="M28.9700376,63.3244248 C47.6273373,55.1957357 60.0684594,49.8368063 66.2934036,47.2476366 C84.0668845,39.855031 87.7600616,38.5708563 90.1672227,38.528 C90.6966555,38.5191258 91.8804274,38.6503351 92.6472251,39.2725385 C93.294694,39.7979149 93.4728387,40.5076237 93.5580865,41.0057381 C93.6433345,41.5038525 93.7494885,42.63857 93.6651041,43.5252052 C92.7019529,53.6451182 88.5344133,78.2034783 86.4142057,89.5379542 C85.5170662,94.3339958 83.750571,95.9420841 82.0403991,96.0994568 C78.3237996,96.4414641 75.5015827,93.6432685 71.9018743,91.2836143 C66.2690414,87.5912212 63.0868492,85.2926952 57.6192095,81.6896017 C51.3004058,77.5256038 55.3966232,75.2369981 58.9976911,71.4967761 C59.9401076,70.5179421 76.3155302,55.6232293 76.6324771,54.2720454 C76.6721165,54.1030573 76.7089039,53.4731496 76.3346867,53.1405352 C75.9604695,52.8079208 75.4081573,52.921662 75.0095933,53.0121213 C74.444641,53.1403447 65.4461175,59.0880351 48.0140228,70.8551922 C45.4598218,72.6091037 43.1463059,73.4636682 41.0734751,73.4188859 C38.7883453,73.3695169 34.3926725,72.1268388 31.1249416,71.0646282 C27.1169366,69.7617838 23.931454,69.0729605 24.208838,66.8603276 C24.3533167,65.7078514 25.9403832,64.5292172 28.9700376,63.3244248 Z" /> + </SVG> +); diff --git a/packages/block-library/src/social-link/icons/tiktok.js b/packages/block-library/src/social-link/icons/tiktok.js new file mode 100644 index 00000000000000..bc76127b02b2a2 --- /dev/null +++ b/packages/block-library/src/social-link/icons/tiktok.js @@ -0,0 +1,10 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/primitives'; + +export const TiktokIcon = () => ( + <SVG width="24" height="24" viewBox="0 0 32 32" version="1.1"> + <Path d="M16.708 0.027c1.745-0.027 3.48-0.011 5.213-0.027 0.105 2.041 0.839 4.12 2.333 5.563 1.491 1.479 3.6 2.156 5.652 2.385v5.369c-1.923-0.063-3.855-0.463-5.6-1.291-0.76-0.344-1.468-0.787-2.161-1.24-0.009 3.896 0.016 7.787-0.025 11.667-0.104 1.864-0.719 3.719-1.803 5.255-1.744 2.557-4.771 4.224-7.88 4.276-1.907 0.109-3.812-0.411-5.437-1.369-2.693-1.588-4.588-4.495-4.864-7.615-0.032-0.667-0.043-1.333-0.016-1.984 0.24-2.537 1.495-4.964 3.443-6.615 2.208-1.923 5.301-2.839 8.197-2.297 0.027 1.975-0.052 3.948-0.052 5.923-1.323-0.428-2.869-0.308-4.025 0.495-0.844 0.547-1.485 1.385-1.819 2.333-0.276 0.676-0.197 1.427-0.181 2.145 0.317 2.188 2.421 4.027 4.667 3.828 1.489-0.016 2.916-0.88 3.692-2.145 0.251-0.443 0.532-0.896 0.547-1.417 0.131-2.385 0.079-4.76 0.095-7.145 0.011-5.375-0.016-10.735 0.025-16.093z" /> + </SVG> +); diff --git a/packages/block-library/src/social-link/index.php b/packages/block-library/src/social-link/index.php index 7a59285c929c8a..67186641616d1d 100644 --- a/packages/block-library/src/social-link/index.php +++ b/packages/block-library/src/social-link/index.php @@ -190,6 +190,10 @@ function block_core_social_link_services( $service = '', $field = '' ) { 'name' => 'Medium', 'icon' => '<svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" role="img" aria-hidden="true" focusable="false"><path d="M20.962,7.257l-5.457,8.867l-3.923-6.375l3.126-5.08c0.112-0.182,0.319-0.286,0.527-0.286c0.05,0,0.1,0.008,0.149,0.02 c0.039,0.01,0.078,0.023,0.114,0.041l5.43,2.715l0.006,0.003c0.004,0.002,0.007,0.006,0.011,0.008 C20.971,7.191,20.98,7.227,20.962,7.257z M9.86,8.592v5.783l5.14,2.57L9.86,8.592z M15.772,17.331l4.231,2.115 C20.554,19.721,21,19.529,21,19.016V8.835L15.772,17.331z M8.968,7.178L3.665,4.527C3.569,4.479,3.478,4.456,3.395,4.456 C3.163,4.456,3,4.636,3,4.938v11.45c0,0.306,0.224,0.669,0.498,0.806l4.671,2.335c0.12,0.06,0.234,0.088,0.337,0.088 c0.29,0,0.494-0.225,0.494-0.602V7.231C9,7.208,8.988,7.188,8.968,7.178z"></path></svg>', ), + 'patreon' => array( + 'name' => 'Patreon', + 'icon' => '<svg width="24" height="24" viewBox="0 0 569 546" version="1.1" xmlns="http://www.w3.org/2000/svg" role="img" aria-hidden="true" focusable="false"><circle cx="363" cy="205" r="205" /><rect width="100" height="546" x="0" y="0" /></svg>', + ), 'pinterest' => array( 'name' => 'Pinterest', 'icon' => '<svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" role="img" aria-hidden="true" focusable="false"><path d="M12.289,2C6.617,2,3.606,5.648,3.606,9.622c0,1.846,1.025,4.146,2.666,4.878c0.25,0.111,0.381,0.063,0.439-0.169 c0.044-0.175,0.267-1.029,0.365-1.428c0.032-0.128,0.017-0.237-0.091-0.362C6.445,11.911,6.01,10.75,6.01,9.668 c0-2.777,2.194-5.464,5.933-5.464c3.23,0,5.49,2.108,5.49,5.122c0,3.407-1.794,5.768-4.13,5.768c-1.291,0-2.257-1.021-1.948-2.277 c0.372-1.495,1.089-3.112,1.089-4.191c0-0.967-0.542-1.775-1.663-1.775c-1.319,0-2.379,1.309-2.379,3.059 c0,1.115,0.394,1.869,0.394,1.869s-1.302,5.279-1.54,6.261c-0.405,1.666,0.053,4.368,0.094,4.604 c0.021,0.126,0.167,0.169,0.25,0.063c0.129-0.165,1.699-2.419,2.142-4.051c0.158-0.59,0.817-2.995,0.817-2.995 c0.43,0.784,1.681,1.446,3.013,1.446c3.963,0,6.822-3.494,6.822-7.833C20.394,5.112,16.849,2,12.289,2"></path></svg>', @@ -218,6 +222,14 @@ function block_core_social_link_services( $service = '', $field = '' ) { 'name' => 'Spotify', 'icon' => '<svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" role="img" aria-hidden="true" focusable="false"><path d="M12,2C6.477,2,2,6.477,2,12c0,5.523,4.477,10,10,10c5.523,0,10-4.477,10-10C22,6.477,17.523,2,12,2 M16.586,16.424 c-0.18,0.295-0.563,0.387-0.857,0.207c-2.348-1.435-5.304-1.76-8.785-0.964c-0.335,0.077-0.67-0.133-0.746-0.469 c-0.077-0.335,0.132-0.67,0.469-0.746c3.809-0.871,7.077-0.496,9.713,1.115C16.673,15.746,16.766,16.13,16.586,16.424 M17.81,13.7 c-0.226,0.367-0.706,0.482-1.072,0.257c-2.687-1.652-6.785-2.131-9.965-1.166C6.36,12.917,5.925,12.684,5.8,12.273 C5.675,11.86,5.908,11.425,6.32,11.3c3.632-1.102,8.147-0.568,11.234,1.328C17.92,12.854,18.035,13.335,17.81,13.7 M17.915,10.865 c-3.223-1.914-8.54-2.09-11.618-1.156C5.804,9.859,5.281,9.58,5.131,9.086C4.982,8.591,5.26,8.069,5.755,7.919 c3.532-1.072,9.404-0.865,13.115,1.338c0.445,0.264,0.59,0.838,0.327,1.282C18.933,10.983,18.359,11.129,17.915,10.865"></path></svg>', ), + 'telegram' => array( + 'name' => 'Telegram', + 'icon' => '<svg width="24" height="24" viewBox="0 0 128 128" version="1.1" xmlns="http://www.w3.org/2000/svg" role="img" aria-hidden="true" focusable="false"><path d="M28.9700376,63.3244248 C47.6273373,55.1957357 60.0684594,49.8368063 66.2934036,47.2476366 C84.0668845,39.855031 87.7600616,38.5708563 90.1672227,38.528 C90.6966555,38.5191258 91.8804274,38.6503351 92.6472251,39.2725385 C93.294694,39.7979149 93.4728387,40.5076237 93.5580865,41.0057381 C93.6433345,41.5038525 93.7494885,42.63857 93.6651041,43.5252052 C92.7019529,53.6451182 88.5344133,78.2034783 86.4142057,89.5379542 C85.5170662,94.3339958 83.750571,95.9420841 82.0403991,96.0994568 C78.3237996,96.4414641 75.5015827,93.6432685 71.9018743,91.2836143 C66.2690414,87.5912212 63.0868492,85.2926952 57.6192095,81.6896017 C51.3004058,77.5256038 55.3966232,75.2369981 58.9976911,71.4967761 C59.9401076,70.5179421 76.3155302,55.6232293 76.6324771,54.2720454 C76.6721165,54.1030573 76.7089039,53.4731496 76.3346867,53.1405352 C75.9604695,52.8079208 75.4081573,52.921662 75.0095933,53.0121213 C74.444641,53.1403447 65.4461175,59.0880351 48.0140228,70.8551922 C45.4598218,72.6091037 43.1463059,73.4636682 41.0734751,73.4188859 C38.7883453,73.3695169 34.3926725,72.1268388 31.1249416,71.0646282 C27.1169366,69.7617838 23.931454,69.0729605 24.208838,66.8603276 C24.3533167,65.7078514 25.9403832,64.5292172 28.9700376,63.3244248 Z" /></svg>', + ), + 'tiktok' => array( + 'name' => 'TikTok', + 'icon' => '<svg width="24" height="24" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" role="img" aria-hidden="true" focusable="false"><path d="M16.708 0.027c1.745-0.027 3.48-0.011 5.213-0.027 0.105 2.041 0.839 4.12 2.333 5.563 1.491 1.479 3.6 2.156 5.652 2.385v5.369c-1.923-0.063-3.855-0.463-5.6-1.291-0.76-0.344-1.468-0.787-2.161-1.24-0.009 3.896 0.016 7.787-0.025 11.667-0.104 1.864-0.719 3.719-1.803 5.255-1.744 2.557-4.771 4.224-7.88 4.276-1.907 0.109-3.812-0.411-5.437-1.369-2.693-1.588-4.588-4.495-4.864-7.615-0.032-0.667-0.043-1.333-0.016-1.984 0.24-2.537 1.495-4.964 3.443-6.615 2.208-1.923 5.301-2.839 8.197-2.297 0.027 1.975-0.052 3.948-0.052 5.923-1.323-0.428-2.869-0.308-4.025 0.495-0.844 0.547-1.485 1.385-1.819 2.333-0.276 0.676-0.197 1.427-0.181 2.145 0.317 2.188 2.421 4.027 4.667 3.828 1.489-0.016 2.916-0.88 3.692-2.145 0.251-0.443 0.532-0.896 0.547-1.417 0.131-2.385 0.079-4.76 0.095-7.145 0.011-5.375-0.016-10.735 0.025-16.093z" /></svg>', + ), 'tumblr' => array( 'name' => 'Tumblr', 'icon' => '<svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" role="img" aria-hidden="true" focusable="false"><path d="M17.04 21.28h-3.28c-2.84 0-4.94-1.37-4.94-5.02v-5.67H6.08V7.5c2.93-.73 4.11-3.3 4.3-5.48h3.01v4.93h3.47v3.65H13.4v4.93c0 1.47.73 2.01 1.92 2.01h1.73v3.75z" /></path></svg>', diff --git a/packages/block-library/src/social-link/socials-with-bg.scss b/packages/block-library/src/social-link/socials-with-bg.scss index ec46f3626bf4d0..a94b8560ba9ef1 100644 --- a/packages/block-library/src/social-link/socials-with-bg.scss +++ b/packages/block-library/src/social-link/socials-with-bg.scss @@ -108,6 +108,11 @@ color: #fff; } +.wp-social-link-patreon { + background-color: #ff424d; + color: #fff; +} + .wp-social-link-pinterest { background-color: #e60122; color: #fff; @@ -144,6 +149,16 @@ color: #fff; } +.wp-social-link-telegram { + background-color: #2aabee; + color: #fff; +} + +.wp-social-link-tiktok { + background-color: #000; + color: #fff; +} + .wp-social-link-tumblr { background-color: #011835; color: #fff; diff --git a/packages/block-library/src/social-link/socials-without-bg.scss b/packages/block-library/src/social-link/socials-without-bg.scss index 89d05e6fd37cb9..2bb6b65d0c03b0 100644 --- a/packages/block-library/src/social-link/socials-without-bg.scss +++ b/packages/block-library/src/social-link/socials-without-bg.scss @@ -82,6 +82,10 @@ color: #f6405f; } +.wp-social-link-patreon { + color: #ff424d; +} + .wp-social-link-pinterest { color: #e60122; } @@ -111,6 +115,14 @@ color: #1bd760; } +.wp-social-link-telegram { + color: #2aabee; +} + +.wp-social-link-tiktok { + color: #000; +} + .wp-social-link-tumblr { color: #011835; } diff --git a/packages/block-library/src/social-link/variations.js b/packages/block-library/src/social-link/variations.js index 028495590bcaac..4297ced3c41718 100644 --- a/packages/block-library/src/social-link/variations.js +++ b/packages/block-library/src/social-link/variations.js @@ -26,6 +26,7 @@ import { MastodonIcon, MeetupIcon, MediumIcon, + PatreonIcon, PinterestIcon, PocketIcon, RedditIcon, @@ -33,6 +34,8 @@ import { SnapchatIcon, SoundCloudIcon, SpotifyIcon, + TelegramIcon, + TiktokIcon, TumblrIcon, TwitchIcon, TwitterIcon, @@ -197,6 +200,12 @@ const variations = [ title: 'Medium', icon: MediumIcon, }, + { + name: 'patreon', + attributes: { service: 'patreon' }, + title: 'Patreon', + icon: PatreonIcon, + }, { name: 'pinterest', attributes: { service: 'pinterest' }, @@ -239,6 +248,18 @@ const variations = [ title: 'Spotify', icon: SpotifyIcon, }, + { + name: 'telegram', + attributes: { service: 'telegram' }, + title: 'Telegram', + icon: TelegramIcon, + }, + { + name: 'tiktok', + attributes: { service: 'tiktok' }, + title: 'TikTok', + icon: TiktokIcon, + }, { name: 'tumblr', attributes: { service: 'tumblr' }, diff --git a/packages/block-library/src/social-links/block.json b/packages/block-library/src/social-links/block.json index d0ed132031b912..2bc44dd79f36e7 100644 --- a/packages/block-library/src/social-links/block.json +++ b/packages/block-library/src/social-links/block.json @@ -6,6 +6,9 @@ "openInNewTab": { "type": "boolean", "default": false + }, + "size": { + "type": "string" } }, "providesContext": { @@ -18,5 +21,7 @@ "right" ], "anchor": true - } + }, + "editorStyle": "wp-block-social-links-editor", + "style": "wp-block-social-links" } diff --git a/packages/block-library/src/social-links/edit.js b/packages/block-library/src/social-links/edit.js index 13ce364c83561b..b7d78bcf87821e 100644 --- a/packages/block-library/src/social-links/edit.js +++ b/packages/block-library/src/social-links/edit.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classNames from 'classnames'; + /** * WordPress dependencies */ @@ -5,30 +10,51 @@ import { Fragment } from '@wordpress/element'; import { + BlockControls, __experimentalUseInnerBlocksProps as useInnerBlocksProps, useBlockProps, InspectorControls, } from '@wordpress/block-editor'; -import { ToggleControl, PanelBody } from '@wordpress/components'; +import { + DropdownMenu, + MenuGroup, + MenuItem, + PanelBody, + ToggleControl, + ToolbarItem, + ToolbarGroup, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { check } from '@wordpress/icons'; const ALLOWED_BLOCKS = [ 'core/social-link' ]; +const sizeOptions = [ + { name: __( 'Small' ), value: 'has-small-icon-size' }, + { name: __( 'Normal' ), value: 'has-normal-icon-size' }, + { name: __( 'Large' ), value: 'has-large-icon-size' }, + { name: __( 'Huge' ), value: 'has-huge-icon-size' }, +]; + export function SocialLinksEdit( props ) { const { - attributes: { openInNewTab }, + attributes: { size, openInNewTab }, setAttributes, } = props; const SocialPlaceholder = ( <div className="wp-block-social-links__social-placeholder"> - <div className="wp-block-social-link wp-social-link-facebook"></div> - <div className="wp-block-social-link wp-social-link-twitter"></div> - <div className="wp-block-social-link wp-social-link-instagram"></div> + <div className="wp-social-link"></div> + <div className="wp-block-social-links__social-placeholder-icons"> + <div className="wp-social-link wp-social-link-twitter"></div> + <div className="wp-social-link wp-social-link-facebook"></div> + <div className="wp-social-link wp-social-link-instagram"></div> + </div> </div> ); - const blockProps = useBlockProps(); + const className = classNames( size ); + const blockProps = useBlockProps( { className } ); const innerBlocksProps = useInnerBlocksProps( blockProps, { allowedBlocks: ALLOWED_BLOCKS, orientation: 'horizontal', @@ -36,8 +62,61 @@ export function SocialLinksEdit( props ) { templateLock: false, __experimentalAppenderTagName: 'li', } ); + + const POPOVER_PROPS = { + position: 'bottom right', + isAlternate: true, + }; + return ( <Fragment> + <BlockControls> + <ToolbarGroup> + <ToolbarItem> + { ( toggleProps ) => ( + <DropdownMenu + label={ __( 'Size' ) } + text={ __( 'Size' ) } + icon={ null } + popoverProps={ POPOVER_PROPS } + toggleProps={ toggleProps } + > + { ( { onClose } ) => ( + <MenuGroup> + { sizeOptions.map( ( entry ) => { + return ( + <MenuItem + icon={ + ( size === + entry.value || + ( ! size && + entry.value === + 'has-normal-icon-size' ) ) && + check + } + isSelected={ + size === entry.value + } + key={ entry.value } + onClick={ () => { + setAttributes( { + size: entry.value, + } ); + } } + onClose={ onClose } + role="menuitemradio" + > + { entry.name } + </MenuItem> + ); + } ) } + </MenuGroup> + ) } + </DropdownMenu> + ) } + </ToolbarItem> + </ToolbarGroup> + </BlockControls> <InspectorControls> <PanelBody title={ __( 'Link settings' ) }> <ToggleControl diff --git a/packages/block-library/src/social-links/editor.scss b/packages/block-library/src/social-links/editor.scss index dec1e6fa3634d0..ec7d7962dd19cd 100644 --- a/packages/block-library/src/social-links/editor.scss +++ b/packages/block-library/src/social-links/editor.scss @@ -6,17 +6,9 @@ } } -// Reduce the paddings, margins, and UI of inner-blocks. -// @todo: eventually we may add a feature that lets a parent container absorb the block UI of a child block. -// When that happens, leverage that instead of the following overrides. -.editor-styles-wrapper .wp-block-social-link { - margin: 0 8px 8px 0; - - // Prevent toolbar from jumping when selecting / hovering a link. - &.is-selected, - &.is-hovered { - transform: none; - } +// Prevent toolbar from jumping when selecting / hovering a link. +.wp-social-link:hover { + transform: none; } .editor-styles-wrapper .wp-block-social-links { @@ -26,42 +18,82 @@ // Placeholder/setup state. .wp-block-social-links__social-placeholder { display: flex; + opacity: 0.8; + transition: all 0.1s ease-in-out; + @include reduce-motion("transition"); - .wp-block-social-link { - display: inline-flex; - align-items: center; - justify-content: center; - margin: 0 $grid-unit-10 $grid-unit-10 0; - width: 36px; - height: 36px; - border-radius: $radius-round; - opacity: 0.8; + .is-selected & { + opacity: 0.1; } -} -.is-style-logos-only .wp-block-social-links__social-placeholder .wp-block-social-link::before { - content: ""; - display: block; - width: 18px; - height: 18px; - border-radius: $radius-round; - opacity: 0.8; - background: currentColor; -} + // Use the first link to set the height. + > .wp-social-link { + // Use !important to keep the selector simple. + padding-left: 0 !important; + margin-left: 0 !important; + padding-right: 0 !important; + margin-right: 0 !important; + width: 0 !important; + visibility: hidden; + } + + // Wrap the remaining placeholders in a container so the plus can overlap. + > .wp-block-social-links__social-placeholder-icons { + display: flex; + position: absolute; + } + + & + .block-list-appender, + .wp-social-link { + padding: 0.25em; + + .is-style-pill-shape & { + padding-left: calc((2/3) * 1em); + padding-right: calc((2/3) * 1em); + } + } + + .wp-social-link::before { + content: ""; + display: block; + width: 1em; + height: 1em; + border-radius: $radius-round; -.is-style-pill-shape .wp-block-social-links__social-placeholder .wp-block-social-link { - border-radius: 9999px; - padding-left: 16px + 12px; - padding-right: 16px + 12px; + .is-style-logos-only & { + background: currentColor; + } + } } // Polish the Appender. .wp-block-social-links .block-list-appender { - margin: 0 0 $grid-unit-10 0; display: flex; align-items: center; + justify-content: center; + margin: 0; + + &::before { + content: ""; + display: block; + width: 1em; + height: 1em; + } + + .block-editor-inserter { + position: absolute; + } + + .block-editor-button-block-appender.block-list-appender__toggle { + margin: 0; + } } +.wp-block-social-links.is-style-logos-only .block-list-appender { + padding: 4px; +} + + // Center flex items. This has an equivalent in style.scss. .wp-block[data-align="center"] > .wp-block-social-links { justify-content: center; @@ -69,16 +101,10 @@ // Improve the preview, ensure buttons are fully opaque despite being disabled. // @todo: Look at improving the preview component to make this unnecessary. -.block-editor-block-preview__content .wp-social-link:disabled { +.block-editor-block-preview__content .components-button:disabled { opacity: 1; } -// Selected/unselected states. -// Unselected block is preview, selected has additional options. -[data-type="core/social-links"]:not(.is-selected):not(.has-child-selected) .wp-block-social-links { - min-height: 36px; // This height matches the height of the buttons and ensures an empty block doesn't collapse. -} - // Unconfigured placeholder links are semitransparent. .wp-social-link.wp-social-link__is-incomplete { opacity: 0.5; @@ -101,10 +127,3 @@ // Windows High Contrast mode will show this outline, but not the box-shadow. outline: 2px solid transparent; } - -// To ensure a better selection footprint when editing, attach the margin to the block container. -// @todo: This can very probably be removed entirely when this block receives a lighter DOM. -.is-navigate-mode .block-editor-block-list__layout .block-editor-block-list__block[data-type="core/social-link"].is-selected::after, -.block-editor-block-list__layout .block-editor-block-list__block[data-type="core/social-link"]:not([contenteditable]):focus::after { - right: 8px; -} diff --git a/packages/block-library/src/social-links/save.js b/packages/block-library/src/social-links/save.js index 23126d88d6d8c8..09728cd12fc150 100644 --- a/packages/block-library/src/social-links/save.js +++ b/packages/block-library/src/social-links/save.js @@ -1,11 +1,22 @@ +/** + * External dependencies + */ +import classNames from 'classnames'; + /** * WordPress dependencies */ import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; -export default function save() { +export default function save( props ) { + const { + attributes: { size }, + } = props; + + const className = classNames( size ); + return ( - <ul { ...useBlockProps.save() }> + <ul { ...useBlockProps.save( { className } ) }> <InnerBlocks.Content /> </ul> ); diff --git a/packages/block-library/src/social-links/style.scss b/packages/block-library/src/social-links/style.scss index cfd5375654b362..b95d01c771bd79 100644 --- a/packages/block-library/src/social-links/style.scss +++ b/packages/block-library/src/social-links/style.scss @@ -16,19 +16,70 @@ border-bottom: 0; box-shadow: none; } + + // Vertically balance the margin of each icon. + .wp-social-link { + // This needs specificity to override some themes. + &.wp-social-link.wp-social-link { + margin: 4px 8px 4px 0; + } + + // By setting the font size, we can scale icons and paddings consistently based on that. + // This also allows themes to override this, if need be. + a { + padding: 0.25em; + } + + svg { + width: 1em; + height: 1em; + } + } + + // Icon sizes. + // Small. + &.has-small-icon-size { + font-size: 16px; // 16 makes for a quarter-padding that keeps the icon centered. + } + + // Normal/default. + &, + &.has-normal-icon-size { + font-size: 24px; + } + + // Large. + &.has-large-icon-size { + font-size: 36px; + } + + // Huge. + &.has-huge-icon-size { + font-size: 48px; + } + + // Center flex items. This has an equivalent in editor.scss. + // It also needs to override some of the default classes usually applied to the centering class. + // align left must not be set, because this is the default (flex-start). + &.aligncenter { + justify-content: center; + display: flex; + } + &.alignright { + justify-content: flex-end; + } } .wp-social-link { display: block; - width: 36px; - height: 36px; border-radius: 9999px; // This makes it pill-shaped instead of oval, in cases where the image fed is not perfectly sized. - margin: 0 8px 8px 0; transition: transform 0.1s ease; @include reduce-motion("transition"); + // Dimensions. + height: auto; + a { - padding: 6px; display: block; line-height: 0; transition: transform 0.1s ease; @@ -48,13 +99,6 @@ } } -// Center flex items. This has an equivalent in editor.scss. -// It also needs to override some of the default classes usually applied to the centering class. -.wp-block-social-links.aligncenter { - justify-content: center; - display: flex; -} - // Provide colors for a range of icons. .wp-block-social-links:not(.is-style-logos-only) { @@ -69,10 +113,6 @@ // Make these bigger. padding: 4px; - svg { - width: 28px; - height: 28px; - } } @import "../social-link/socials-without-bg.scss"; @@ -85,7 +125,7 @@ } .wp-social-link a { - padding-left: 16px; - padding-right: 16px; + padding-left: calc((2/3) * 1em); + padding-right: calc((2/3) * 1em); } } diff --git a/packages/block-library/src/spacer/block.json b/packages/block-library/src/spacer/block.json index 9882f53dfa5af2..fd4172079fa50c 100644 --- a/packages/block-library/src/spacer/block.json +++ b/packages/block-library/src/spacer/block.json @@ -10,5 +10,7 @@ }, "supports": { "anchor": true - } + }, + "editorStyle": "wp-block-spacer-editor", + "style": "wp-block-spacer" } diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index f4cfe22788b872..3a44e690c5e82e 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -8,6 +8,7 @@ @import "./buttons/style.scss"; @import "./calendar/style.scss"; @import "./categories/style.scss"; +@import "./code/style.scss"; @import "./columns/style.scss"; @import "./cover/style.scss"; @import "./embed/style.scss"; @@ -24,7 +25,10 @@ @import "./navigation-link/style.scss"; @import "./paragraph/style.scss"; @import "./post-author/style.scss"; +@import "./post-comments-form/style.scss"; +@import "./preformatted/style.scss"; @import "./pullquote/style.scss"; +@import "./query-loop/style.scss"; @import "./quote/style.scss"; @import "./rss/style.scss"; @import "./search/style.scss"; @@ -33,74 +37,11 @@ @import "./social-links/style.scss"; @import "./spacer/style.scss"; @import "./subhead/style.scss"; +@import "./tag-cloud/style.scss"; @import "./table/style.scss"; @import "./text-columns/style.scss"; +@import "./verse/style.scss"; @import "./video/style.scss"; @import "./post-featured-image/style.scss"; -// The following selectors have increased specificity (using the :root prefix) -// to assure colors take effect over another base class color, mainly to let -// the colors override the added specificity by link states such as :hover. - -:root { - // Background colors. - @include background-colors(); - - // Foreground colors. - @include foreground-colors(); - - // Gradients - @include gradient-colors(); - - .has-link-color a { - color: var(--wp--style--color--link, #00e); - } -} - -// Font sizes. -.has-small-font-size { - font-size: 0.8125em; -} - -.has-regular-font-size, // Not used now, kept because of backward compatibility. -.has-normal-font-size { - font-size: 1em; -} - -.has-medium-font-size { - font-size: 1.25em; -} - -.has-large-font-size { - font-size: 2.25em; -} - -.has-larger-font-size, // Not used now, kept because of backward compatibility. -.has-huge-font-size { - font-size: 2.625em; -} - -// Text alignments. -.has-text-align-center { - text-align: center; -} - -.has-text-align-left { - /*rtl:ignore*/ - text-align: left; -} - -.has-text-align-right { - /*rtl:ignore*/ - text-align: right; -} - -// This tag marks the end of the styles that apply to editing canvas contents and need to be manipulated when we resize the editor. -#end-resizable-editor-section { - display: none; -} - -// Block alignments. -.aligncenter { - clear: both; -} +@import "common.scss"; diff --git a/packages/block-library/src/subhead/block.json b/packages/block-library/src/subhead/block.json index 6fe2bf287f7eac..2dd213d5391d05 100644 --- a/packages/block-library/src/subhead/block.json +++ b/packages/block-library/src/subhead/block.json @@ -15,5 +15,7 @@ "supports": { "inserter": false, "multiple": false - } + }, + "editorStyle": "wp-block-subhead-editor", + "style": "wp-block-subhead" } diff --git a/packages/block-library/src/subhead/edit.js b/packages/block-library/src/subhead/edit.js index f9551eaad3a77e..c0317ba6ef9801 100644 --- a/packages/block-library/src/subhead/edit.js +++ b/packages/block-library/src/subhead/edit.js @@ -43,6 +43,7 @@ export default function SubheadEdit( { } } style={ { textAlign: align } } className={ className } + aria-label={ __( 'Subheading text' ) } placeholder={ placeholder || __( 'Write subheading…' ) } /> </div> diff --git a/packages/block-library/src/table/block.json b/packages/block-library/src/table/block.json index 3e94d2abf54f17..65e907620afb51 100644 --- a/packages/block-library/src/table/block.json +++ b/packages/block-library/src/table/block.json @@ -125,6 +125,8 @@ "supports": { "anchor": true, "align": true, - "__experimentalSelector": ".wp-block-button > table" - } + "__experimentalSelector": ".wp-block-table > table" + }, + "editorStyle": "wp-block-table-editor", + "style": "wp-block-table" } diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index 941516ed295c12..5876f7f956c250 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -101,6 +101,12 @@ const ALIGNMENT_CONTROLS = [ const withCustomBackgroundColors = createCustomColorsHOC( BACKGROUND_COLORS ); +const cellAriaLabel = { + head: __( 'Header cell text' ), + body: __( 'Body cell text' ), + foot: __( 'Footer cell text' ), +}; + const placeholder = { head: __( 'Header label' ), foot: __( 'Footer label' ), @@ -432,6 +438,7 @@ function TableEdit( { type: 'cell', } ); } } + aria-label={ cellAriaLabel[ name ] } placeholder={ placeholder[ name ] } /> ) @@ -520,6 +527,7 @@ function TableEdit( { { ! isEmpty && ( <RichText tagName="figcaption" + aria-label={ __( 'Table caption text' ) } placeholder={ __( 'Write caption…' ) } value={ caption } onChange={ ( value ) => diff --git a/packages/block-library/src/tag-cloud/block.json b/packages/block-library/src/tag-cloud/block.json index 3ec21ed2765ccb..de4b9b9714f664 100644 --- a/packages/block-library/src/tag-cloud/block.json +++ b/packages/block-library/src/tag-cloud/block.json @@ -15,5 +15,6 @@ "supports": { "html": false, "align": true - } + }, + "editorStyle": "wp-block-tag-cloud-editor" } diff --git a/packages/block-library/src/tag-cloud/editor.scss b/packages/block-library/src/tag-cloud/editor.scss index c771bc6df78dbd..a554d72ee004e7 100644 --- a/packages/block-library/src/tag-cloud/editor.scss +++ b/packages/block-library/src/tag-cloud/editor.scss @@ -1,4 +1,4 @@ -.block-editor .wp-block-tag-cloud { +.wp-block-tag-cloud { a { display: inline-block; margin-right: 5px; diff --git a/packages/block-library/src/tag-cloud/style.scss b/packages/block-library/src/tag-cloud/style.scss new file mode 100644 index 00000000000000..3592e5d2473142 --- /dev/null +++ b/packages/block-library/src/tag-cloud/style.scss @@ -0,0 +1,10 @@ +.wp-block-tag-cloud { + &.aligncenter { + text-align: center; + } + + &.alignfull { + padding-left: 1em; + padding-right: 1em; + } +} diff --git a/packages/block-library/src/template-part/block.json b/packages/block-library/src/template-part/block.json index 2cf99160a027b2..3d9e5dfa0476e9 100644 --- a/packages/block-library/src/template-part/block.json +++ b/packages/block-library/src/template-part/block.json @@ -24,5 +24,6 @@ "gradients": true, "link": true } - } + }, + "editorStyle": "wp-block-template-part-editor" } diff --git a/packages/block-library/src/template-part/edit/index.js b/packages/block-library/src/template-part/edit/index.js index 0a124995c166ed..e96f85d7ecadbc 100644 --- a/packages/block-library/src/template-part/edit/index.js +++ b/packages/block-library/src/template-part/edit/index.js @@ -3,8 +3,13 @@ */ import { useRef, useEffect } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; -import { BlockControls, useBlockProps } from '@wordpress/block-editor'; import { + BlockControls, + InspectorAdvancedControls, + useBlockProps, +} from '@wordpress/block-editor'; +import { + SelectControl, Dropdown, ToolbarGroup, ToolbarButton, @@ -12,7 +17,7 @@ import { } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { chevronUp, chevronDown } from '@wordpress/icons'; - +import { serialize } from '@wordpress/blocks'; /** * Internal dependencies */ @@ -27,44 +32,64 @@ export default function TemplatePartEdit( { setAttributes, clientId, } ) { - const initialPostId = useRef( _postId ); const initialSlug = useRef( slug ); const initialTheme = useRef( theme ); + const initialContent = useRef(); // Resolve the post ID if not set, and load its post. const postId = useTemplatePartPost( _postId, slug, theme ); - // Set the post ID, once found, so that edits persist, - // but wait until the third inner blocks change, - // because the first 2 are just the template part - // content loading. - const { innerBlocks } = useSelect( + // Set the postId block attribute if it did not exist, + // but wait until the inner blocks have loaded to allow + // new edits to trigger this. + const { innerBlocks, expectedContent } = useSelect( ( select ) => { const { getBlocks } = select( 'core/block-editor' ); + const entityRecord = select( 'core' ).getEntityRecord( + 'postType', + 'wp_template_part', + postId + ); + return { innerBlocks: getBlocks( clientId ), + expectedContent: entityRecord?.content.raw, }; }, - [ clientId ] + [ clientId, postId ] ); const { editEntityRecord } = useDispatch( 'core' ); - const blockChanges = useRef( 0 ); useEffect( () => { - if ( blockChanges.current < 4 ) blockChanges.current++; + // If postId (entity) has not resolved or _postId (block attr) is set, + // then we have no need for this effect. + if ( ! postId || _postId ) { + return; + } + + const innerContent = serialize( innerBlocks ); - if ( - blockChanges.current === 3 && - ( initialPostId.current === undefined || - initialPostId.current === null ) && - postId !== undefined && - postId !== null - ) { + // If we havent set initialContent, check if innerBlocks are loaded. + if ( ! initialContent.current ) { + // If the content of innerBlocks and the content from entity match, + // then we can consider innerBlocks as loaded and set initialContent. + if ( innerContent === expectedContent ) { + initialContent.current = innerContent; + } + // Continue to return early until this effect is triggered + // with innerBlocks already loaded (as denoted by initialContent being set). + return; + } + + // After initialContent is set and the content is updated, we can set the + // postId block attribute and set the post status to 'publish'. + // After this is done the hook will no longer run due to the first return above. + if ( initialContent.current !== innerContent ) { setAttributes( { postId } ); editEntityRecord( 'postType', 'wp_template_part', postId, { status: 'publish', } ); } - }, [ innerBlocks ] ); + }, [ innerBlocks, expectedContent ] ); const blockProps = useBlockProps(); @@ -76,50 +101,77 @@ export default function TemplatePartEdit( { // Part of a template file, post ID not resolved yet. const isUnresolvedTemplateFile = ! isPlaceholder && ! postId; + const inspectorAdvancedControls = ( + <InspectorAdvancedControls> + <SelectControl + label={ __( 'HTML element' ) } + options={ [ + { label: __( 'Default (<div>)' ), value: 'div' }, + { label: '<header>', value: 'header' }, + { label: '<main>', value: 'main' }, + { label: '<section>', value: 'section' }, + { label: '<article>', value: 'article' }, + { label: '<aside>', value: 'aside' }, + { label: '<footer>', value: 'footer' }, + ] } + value={ TagName } + onChange={ ( value ) => setAttributes( { tagName: value } ) } + /> + </InspectorAdvancedControls> + ); + return ( - <TagName { ...blockProps }> - { isPlaceholder && ( - <TemplatePartPlaceholder setAttributes={ setAttributes } /> - ) } - { isTemplateFile && ( - <BlockControls> - <ToolbarGroup className="wp-block-template-part__block-control-group"> - <TemplatePartNamePanel - postId={ postId } - setAttributes={ setAttributes } - /> - <Dropdown - className="wp-block-template-part__preview-dropdown-button" - contentClassName="wp-block-template-part__preview-dropdown-content" - position="bottom right left" - renderToggle={ ( { isOpen, onToggle } ) => ( - <ToolbarButton - aria-expanded={ isOpen } - icon={ isOpen ? chevronUp : chevronDown } - label={ __( 'Choose another' ) } - onClick={ onToggle } - // Disable when open to prevent odd FireFox bug causing reopening. - // As noted in https://github.com/WordPress/gutenberg/pull/24990#issuecomment-689094119 . - disabled={ isOpen } - /> - ) } - renderContent={ ( { onClose } ) => ( - <TemplatePartSelection - setAttributes={ setAttributes } - onClose={ onClose } - /> - ) } - /> - </ToolbarGroup> - </BlockControls> - ) } - { isTemplateFile && ( - <TemplatePartInnerBlocks - postId={ postId } - hasInnerBlocks={ innerBlocks.length > 0 } - /> - ) } - { isUnresolvedTemplateFile && <Spinner /> } - </TagName> + <> + { inspectorAdvancedControls } + <TagName { ...blockProps }> + { isPlaceholder && ( + <TemplatePartPlaceholder + setAttributes={ setAttributes } + innerBlocks={ innerBlocks } + /> + ) } + { isTemplateFile && ( + <BlockControls> + <ToolbarGroup className="wp-block-template-part__block-control-group"> + <TemplatePartNamePanel + postId={ postId } + setAttributes={ setAttributes } + /> + <Dropdown + className="wp-block-template-part__preview-dropdown-button" + contentClassName="wp-block-template-part__preview-dropdown-content" + position="bottom right left" + renderToggle={ ( { isOpen, onToggle } ) => ( + <ToolbarButton + aria-expanded={ isOpen } + icon={ + isOpen ? chevronUp : chevronDown + } + label={ __( 'Choose another' ) } + onClick={ onToggle } + // Disable when open to prevent odd FireFox bug causing reopening. + // As noted in https://github.com/WordPress/gutenberg/pull/24990#issuecomment-689094119 . + disabled={ isOpen } + /> + ) } + renderContent={ ( { onClose } ) => ( + <TemplatePartSelection + setAttributes={ setAttributes } + onClose={ onClose } + /> + ) } + /> + </ToolbarGroup> + </BlockControls> + ) } + { isTemplateFile && ( + <TemplatePartInnerBlocks + postId={ postId } + hasInnerBlocks={ innerBlocks.length > 0 } + /> + ) } + { isUnresolvedTemplateFile && <Spinner /> } + </TagName> + </> ); } diff --git a/packages/block-library/src/template-part/edit/placeholder/index.js b/packages/block-library/src/template-part/edit/placeholder/index.js index 7359538cd00f54..f1181149c86521 100644 --- a/packages/block-library/src/template-part/edit/placeholder/index.js +++ b/packages/block-library/src/template-part/edit/placeholder/index.js @@ -2,18 +2,22 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { useCallback } from '@wordpress/element'; +import { useCallback, useEffect } from '@wordpress/element'; import { useDispatch } from '@wordpress/data'; import { cleanForSlug } from '@wordpress/url'; -import { Placeholder, Dropdown, Button } from '@wordpress/components'; +import { Placeholder, Dropdown, Button, Spinner } from '@wordpress/components'; import { blockDefault } from '@wordpress/icons'; +import { serialize } from '@wordpress/blocks'; /** * Internal dependencies */ import TemplatePartSelection from '../selection'; -export default function TemplatePartPlaceholder( { setAttributes } ) { +export default function TemplatePartPlaceholder( { + setAttributes, + innerBlocks, +} ) { const { saveEntityRecord } = useDispatch( 'core' ); const onCreate = useCallback( async () => { const title = 'Untitled Template Part'; @@ -25,16 +29,31 @@ export default function TemplatePartPlaceholder( { setAttributes } ) { title, status: 'publish', slug, - meta: { theme: 'custom' }, + content: serialize( innerBlocks ), } ); setAttributes( { postId: templatePart.id, slug: templatePart.slug, - theme: templatePart.meta.theme, + theme: templatePart.wp_theme_slug, } ); }, [ setAttributes ] ); + // If there are inner blocks present, the content for creation is clear. + // Therefore immediately create the template part with the given inner blocks as its content. + useEffect( () => { + if ( innerBlocks.length ) { + onCreate(); + } + }, [] ); + if ( innerBlocks.length ) { + return ( + <Placeholder> + <Spinner /> + </Placeholder> + ); + } + return ( <Placeholder icon={ blockDefault } diff --git a/packages/block-library/src/template-part/edit/selection/template-part-previews.js b/packages/block-library/src/template-part/edit/selection/template-part-previews.js index f0f85d9b579bed..b47dfaa92970e5 100644 --- a/packages/block-library/src/template-part/edit/selection/template-part-previews.js +++ b/packages/block-library/src/template-part/edit/selection/template-part-previews.js @@ -9,11 +9,12 @@ import { __, sprintf } from '@wordpress/i18n'; import { BlockPreview } from '@wordpress/block-editor'; import { Icon } from '@wordpress/components'; import { useAsyncList } from '@wordpress/compose'; +import { store as noticesStore } from '@wordpress/notices'; /** * External dependencies */ -import { groupBy, uniq, deburr } from 'lodash'; +import { groupBy, deburr } from 'lodash'; import { Composite, useCompositeState, CompositeItem } from 'reakit'; function PreviewPlaceholder() { @@ -31,16 +32,12 @@ function TemplatePartItem( { onClose, composite, } ) { - const { - id, - slug, - meta: { theme }, - } = templatePart; + const { id, slug, wp_theme_slug: theme } = templatePart; // The 'raw' property is not defined for a brief period in the save cycle. // The fallback prevents an error in the parse function while saving. const content = templatePart.content.raw || ''; const blocks = useMemo( () => parse( content ), [ content ] ); - const { createSuccessNotice } = useDispatch( 'core/notices' ); + const { createSuccessNotice } = useDispatch( noticesStore ); const onClick = useCallback( () => { setAttributes( { postId: id, slug, theme } ); @@ -59,8 +56,9 @@ function TemplatePartItem( { return ( <CompositeItem + as="div" className="wp-block-template-part__selection-preview-item" - role="listitem" + role="option" onClick={ onClick } onKeyDown={ ( event ) => { if ( ENTER === event.keyCode || SPACE === event.keyCode ) { @@ -102,14 +100,15 @@ function TemplatePartsByTheme( { composite, } ) { const templatePartsByTheme = useMemo( () => { - return Object.values( groupBy( templateParts, 'meta.theme' ) ); + return Object.values( groupBy( templateParts, 'wp_theme_slug' ) ); }, [ templateParts ] ); const currentShownTPs = useAsyncList( templateParts ); return templatePartsByTheme.map( ( templatePartList ) => ( <PanelGroup - key={ templatePartList[ 0 ].meta.theme } - title={ templatePartList[ 0 ].meta.theme } + key={ templatePartList[ 0 ].wp_theme_slug } + // Falsy theme implies custom template part. + title={ templatePartList[ 0 ].wp_theme_slug || __( 'Custom' ) } > { templatePartList.map( ( templatePart ) => { return currentShownTPs.includes( templatePart ) ? ( @@ -140,7 +139,7 @@ function TemplatePartSearchResults( { // Remove diacritics and convert to lowercase to normalize. const normalizedFilterValue = deburr( filterValue ).toLowerCase(); const searchResults = templateParts.filter( - ( { slug, meta: { theme } } ) => + ( { slug, wp_theme_slug: theme } ) => slug.toLowerCase().includes( normalizedFilterValue ) || // Since diacritics can be used in theme names, remove them for the comparison. deburr( theme ).toLowerCase().includes( normalizedFilterValue ) @@ -164,10 +163,10 @@ function TemplatePartSearchResults( { // Second prioritize index found in theme. // Since diacritics can be used in theme names, remove them for the comparison. return ( - deburr( a.meta.theme ) + deburr( a.wp_theme_slug ) .toLowerCase() .indexOf( normalizedFilterValue ) - - deburr( b.meta.theme ) + deburr( b.wp_theme_slug ) .toLowerCase() .indexOf( normalizedFilterValue ) ); @@ -178,7 +177,10 @@ function TemplatePartSearchResults( { const currentShownTPs = useAsyncList( filteredTPs ); return filteredTPs.map( ( templatePart ) => ( - <PanelGroup key={ templatePart.id } title={ templatePart.meta.theme }> + <PanelGroup + key={ templatePart.id } + title={ templatePart.wp_theme_slug || __( 'Custom' ) } + > { currentShownTPs.includes( templatePart ) ? ( <TemplatePartItem key={ templatePart.id } @@ -194,40 +196,27 @@ function TemplatePartSearchResults( { ) ); } -export default function TemplateParts( { +export default function TemplatePartPreviews( { setAttributes, filterValue, onClose, } ) { const composite = useCompositeState(); const templateParts = useSelect( ( select ) => { - const publishedTemplateParts = select( 'core' ).getEntityRecords( - 'postType', - 'wp_template_part', - { + const publishedTemplateParts = + select( 'core' ).getEntityRecords( 'postType', 'wp_template_part', { status: [ 'publish' ], per_page: -1, - } - ); - const currentTheme = select( 'core' ).getCurrentTheme()?.stylesheet; + } ) || []; - const themeTemplateParts = select( 'core' ).getEntityRecords( - 'postType', - 'wp_template_part', - { + const currentTheme = select( 'core' ).getCurrentTheme()?.stylesheet; + const themeTemplateParts = + select( 'core' ).getEntityRecords( 'postType', 'wp_template_part', { theme: currentTheme, - status: [ 'publish', 'auto-draft' ], + status: [ 'auto-draft' ], per_page: -1, - } - ); - const combinedTemplateParts = []; - if ( publishedTemplateParts ) { - combinedTemplateParts.push( ...publishedTemplateParts ); - } - if ( themeTemplateParts ) { - combinedTemplateParts.push( ...themeTemplateParts ); - } - return uniq( combinedTemplateParts ); + } ) || []; + return [ ...themeTemplateParts, ...publishedTemplateParts ]; }, [] ); if ( ! templateParts || ! templateParts.length ) { @@ -238,7 +227,7 @@ export default function TemplateParts( { return ( <Composite { ...composite } - role="list" + role="listbox" aria-label={ __( 'List of template parts' ) } > <TemplatePartSearchResults @@ -255,7 +244,7 @@ export default function TemplateParts( { return ( <Composite { ...composite } - role="list" + role="listbox" aria-label={ __( 'List of template parts' ) } > <TemplatePartsByTheme diff --git a/packages/block-library/src/template-part/edit/use-template-part-post.js b/packages/block-library/src/template-part/edit/use-template-part-post.js index 73e027f27eb364..fed17e7a4cd77a 100644 --- a/packages/block-library/src/template-part/edit/use-template-part-post.js +++ b/packages/block-library/src/template-part/edit/use-template-part-post.js @@ -33,19 +33,12 @@ export default function useTemplatePartPost( postId, slug, theme ) { theme, } ); - const foundPosts = posts?.filter( - ( post ) => - post.slug === cleanedSlug && - post.meta && - post.meta.theme === theme - ); + // A published post might already exist if this template part was customized elsewhere // or if it's part of a customized template. const foundPost = - foundPosts?.find( ( post ) => post.status === 'publish' ) || - foundPosts?.find( - ( post ) => post.status === 'auto-draft' - ); + posts?.find( ( post ) => post.status === 'publish' ) || + posts?.find( ( post ) => post.status === 'auto-draft' ); return foundPost?.id; } }, diff --git a/packages/block-library/src/template-part/editor.scss b/packages/block-library/src/template-part/editor.scss index 9c1b5275bf4db1..aed32baffd118f 100644 --- a/packages/block-library/src/template-part/editor.scss +++ b/packages/block-library/src/template-part/editor.scss @@ -89,37 +89,3 @@ box-shadow: 0 0 0 $border-width var(--wp-admin-theme-color); } } - -.block-editor-block-list__block[data-type="core/template-part"] { - &.is-selected, - &.has-child-selected { - &::after { - top: $border-width; - bottom: $border-width; - left: $border-width; - right: $border-width; - border-radius: $radius-block-ui - $border-width; // Border is outset, so so subtract the width to achieve correct radius. - } - } - - &.is-selected { - &::after { - // 2px outside. - box-shadow: 0 0 0 $border-width-focus var(--wp-admin-theme-color); - // Show a light color for dark themes. - .is-dark-theme & { - box-shadow: 0 0 0 $border-width-focus $dark-theme-focus; - } - } - } - - &.has-child-selected { - &::after { - box-shadow: 0 0 0 $border-width $gray-300; - - .is-dark-theme & { - box-shadow: 0 0 0 $border-width-focus $gray-700; - } - } - } -} diff --git a/packages/block-library/src/template-part/index.php b/packages/block-library/src/template-part/index.php index 50629eb83534f3..a960aa969bc906 100644 --- a/packages/block-library/src/template-part/index.php +++ b/packages/block-library/src/template-part/index.php @@ -19,14 +19,19 @@ function render_block_core_template_part( $attributes ) { // If we have a post ID and the post exists, which means this template part // is user-customized, render the corresponding post content. $content = get_post( $attributes['postId'] )->post_content; - } elseif ( isset( $attributes['theme'] ) && wp_get_theme()->stylesheet === $attributes['theme'] ) { + } elseif ( isset( $attributes['theme'] ) && basename( wp_get_theme()->get_stylesheet() ) === $attributes['theme'] ) { $template_part_query = new WP_Query( array( 'post_type' => 'wp_template_part', 'post_status' => 'publish', - 'name' => $attributes['slug'], - 'meta_key' => 'theme', - 'meta_value' => $attributes['theme'], + 'post_name__in' => array( $attributes['slug'] ), + 'tax_query' => array( + array( + 'taxonomy' => 'wp_theme', + 'field' => 'slug', + 'terms' => $attributes['theme'], + ), + ), 'posts_per_page' => 1, 'no_found_rows' => true, ) @@ -40,7 +45,7 @@ function render_block_core_template_part( $attributes ) { // Else, if the template part was provided by the active theme, // render the corresponding file content. $template_part_file_path = get_stylesheet_directory() . '/block-template-parts/' . $attributes['slug'] . '.html'; - if ( 0 === validate_file( $template_part_file_path ) && file_exists( $template_part_file_path ) ) { + if ( 0 === validate_file( $attributes['slug'] ) && file_exists( $template_part_file_path ) ) { $content = file_get_contents( $template_part_file_path ); } } diff --git a/packages/block-library/src/template-part/theme.scss b/packages/block-library/src/template-part/theme.scss index e2480154a14c59..58e7b1fdf2d9ff 100644 --- a/packages/block-library/src/template-part/theme.scss +++ b/packages/block-library/src/template-part/theme.scss @@ -2,8 +2,7 @@ .wp-block-template-part { &.has-background { // Matches paragraph Block padding - // Todo: normalise with variables - padding: 20px 30px; + padding: $block-bg-padding--v $block-bg-padding--h; margin-top: 0; margin-bottom: 0; } diff --git a/packages/block-library/src/text-columns/block.json b/packages/block-library/src/text-columns/block.json index 5cfe5e4712b076..bb52b71b0183da 100644 --- a/packages/block-library/src/text-columns/block.json +++ b/packages/block-library/src/text-columns/block.json @@ -29,5 +29,7 @@ }, "supports": { "inserter": false - } + }, + "editorStyle": "wp-block-text-columns-editor", + "style": "wp-block-text-columns" } diff --git a/packages/block-library/src/text-columns/edit.js b/packages/block-library/src/text-columns/edit.js index dacace06acf605..ec69c75e1a4a9e 100644 --- a/packages/block-library/src/text-columns/edit.js +++ b/packages/block-library/src/text-columns/edit.js @@ -6,7 +6,7 @@ import { get, times } from 'lodash'; /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { PanelBody, RangeControl } from '@wordpress/components'; import { BlockControls, @@ -73,6 +73,11 @@ export default function TextColumnsEdit( { attributes, setAttributes } ) { ], } ); } } + aria-label={ sprintf( + // translators: %d: column index (starting with 1) + __( 'Column %d text' ), + index + 1 + ) } placeholder={ __( 'New Column' ) } /> </div> diff --git a/packages/block-library/src/verse/block.json b/packages/block-library/src/verse/block.json index eec9404d7ab7c9..be3cb69adf0bfd 100644 --- a/packages/block-library/src/verse/block.json +++ b/packages/block-library/src/verse/block.json @@ -15,6 +15,10 @@ } }, "supports": { - "anchor": true - } + "anchor": true, + "__experimentalFontFamily": true, + "fontSize": true + }, + "style": "wp-block-verse", + "editorStyle": "wp-block-verse-editor" } diff --git a/packages/block-library/src/verse/edit.js b/packages/block-library/src/verse/edit.js index 583d228c510a91..a8031e0b0756e0 100644 --- a/packages/block-library/src/verse/edit.js +++ b/packages/block-library/src/verse/edit.js @@ -46,7 +46,8 @@ export default function VerseEdit( { content: nextContent, } ); } } - placeholder={ __( 'Write…' ) } + aria-label={ __( 'Verse text' ) } + placeholder={ __( 'Write verse…' ) } onMerge={ mergeBlocks } textAlign={ textAlign } { ...blockProps } diff --git a/packages/block-library/src/verse/editor.scss b/packages/block-library/src/verse/editor.scss index 6217e8f66ac5ec..f863f5e2556f71 100644 --- a/packages/block-library/src/verse/editor.scss +++ b/packages/block-library/src/verse/editor.scss @@ -1,8 +1,4 @@ pre.wp-block-verse { color: $gray-900; - white-space: nowrap; - font-family: inherit; - font-size: inherit; padding: 1em; - overflow: auto; } diff --git a/packages/block-library/src/verse/style.scss b/packages/block-library/src/verse/style.scss new file mode 100644 index 00000000000000..b13d80b6ddf564 --- /dev/null +++ b/packages/block-library/src/verse/style.scss @@ -0,0 +1,5 @@ +pre.wp-block-verse { + font-family: inherit; + overflow: auto; + white-space: nowrap; +} diff --git a/packages/block-library/src/video/block.json b/packages/block-library/src/video/block.json index e2b7eab17560eb..9f8b569d239b24 100644 --- a/packages/block-library/src/video/block.json +++ b/packages/block-library/src/video/block.json @@ -72,5 +72,7 @@ "supports": { "anchor": true, "align": true - } + }, + "editorStyle": "wp-block-video-editor", + "style": "wp-block-video" } diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index cd3cdfde25ba36..9d540aaf6e8f66 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -235,6 +235,7 @@ function VideoEdit( { { ( ! RichText.isEmpty( caption ) || isSelected ) && ( <RichText tagName="figcaption" + aria-label={ __( 'Video caption text' ) } placeholder={ __( 'Write caption…' ) } value={ caption } onChange={ ( value ) => diff --git a/packages/block-library/src/video/index.js b/packages/block-library/src/video/index.js index 341d95eb289060..ce7d6fd2cdd62f 100644 --- a/packages/block-library/src/video/index.js +++ b/packages/block-library/src/video/index.js @@ -23,6 +23,14 @@ export const settings = { ), icon, keywords: [ __( 'movie' ) ], + example: { + attributes: { + src: + 'https://upload.wikimedia.org/wikipedia/commons/c/ca/Wood_thrush_in_Central_Park_switch_sides_%2816510%29.webm', + // translators: Caption accompanying a video of the wood thrush singing, which serves as an example for the Video block. + caption: __( 'Wood thrush singing in Central Park, NYC.' ), + }, + }, transforms, edit, save, diff --git a/packages/block-library/src/video/style.scss b/packages/block-library/src/video/style.scss index de4e9499dd67c5..0735f6bfe8035c 100644 --- a/packages/block-library/src/video/style.scss +++ b/packages/block-library/src/video/style.scss @@ -4,7 +4,7 @@ margin-right: 0; video { - max-width: 100%; + width: 100%; } @supports (position: sticky) { diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index b610500f6e4573..2a6237f7f6ff43 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -24,7 +24,7 @@ "react-native": "src/index", "sideEffects": false, "dependencies": { - "@babel/runtime": "^7.11.2" + "@babel/runtime": "^7.12.5" }, "publishConfig": { "access": "public" diff --git a/packages/block-serialization-default-parser/src/index.js b/packages/block-serialization-default-parser/src/index.js index c6a844d0cc6dd7..54e76d877dcb02 100644 --- a/packages/block-serialization-default-parser/src/index.js +++ b/packages/block-serialization-default-parser/src/index.js @@ -309,7 +309,7 @@ function nextToken() { // we're also using a trick here because the only difference between a // block opener and a block closer is the leading `/` before `wp:` (and // a closer has no attributes). we can trap them both and process the - // match back in Javascript to see which one it was. + // match back in JavaScript to see which one it was. const matches = tokenizer.exec( document ); // we have no more tokens diff --git a/packages/block-serialization-default-parser/test/test-parser.php b/packages/block-serialization-default-parser/test/test-parser.php index 1391c0edd16846..21f4651aff84ed 100644 --- a/packages/block-serialization-default-parser/test/test-parser.php +++ b/packages/block-serialization-default-parser/test/test-parser.php @@ -8,7 +8,7 @@ */ // Include the default parser. -require_once dirname( __FILE__ ) . '/../parser.php'; +require_once __DIR__ . '/../parser.php'; $parser = new WP_Block_Parser(); diff --git a/packages/block-serialization-spec-parser/test/test-parser.php b/packages/block-serialization-spec-parser/test/test-parser.php index a55183c44b8e5d..38a4f85e802736 100644 --- a/packages/block-serialization-spec-parser/test/test-parser.php +++ b/packages/block-serialization-spec-parser/test/test-parser.php @@ -8,7 +8,7 @@ */ // Include the generated parser. -require_once dirname( __FILE__ ) . '/../parser.php'; +require_once __DIR__ . '/../parser.php'; $parser = new Gutenberg_PEG_Parser(); diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index 4cb3630f37002a..bcdf8b5ee6d734 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -2,38 +2,42 @@ ## Unreleased +### New Feature + +- Added a store definition `store` for the blocks namespace to use with `@wordpress/data` API ([#26655](https://github.com/WordPress/gutenberg/pull/26655)). + ## 6.13.0 (2020-04-01) ### New Feature -- Blocks can now be registered with an `defaultStylePicker` flag in the `supports` setting, allowing the default style picker to be removed. +- Blocks can now be registered with an `defaultStylePicker` flag in the `supports` setting, allowing the default style picker to be removed. ## 6.4.0 (2019-08-05) ### Improvements -- Omitting `attributes` or `keywords` settings will now stub default values (an empty object or empty array, respectively). +- Omitting `attributes` or `keywords` settings will now stub default values (an empty object or empty array, respectively). ### Bug Fixes -- The `'blocks.registerBlockType'` filter is now applied to each of a block's deprecated settings as well as the block's main settings. Ensures `supports` settings like `anchor` work for deprecations. +- The `'blocks.registerBlockType'` filter is now applied to each of a block's deprecated settings as well as the block's main settings. Ensures `supports` settings like `anchor` work for deprecations. ## 6.3.0 (2019-05-21) ### New Feature -- Added a default implementation for `save` setting in `registerBlockType` which saves no markup in the post content. -- Added wildcard block transforms which allows for transforming all/any blocks in another block. +- Added a default implementation for `save` setting in `registerBlockType` which saves no markup in the post content. +- Added wildcard block transforms which allows for transforming all/any blocks in another block. ## 6.1.0 (2019-03-06) ### New Feature -- Blocks' `transforms` will receive `innerBlocks` as the second argument (or an array of each block's respective `innerBlocks` for a multi-transform). +- Blocks' `transforms` will receive `innerBlocks` as the second argument (or an array of each block's respective `innerBlocks` for a multi-transform). ### Bug Fixes -- Block validation will now correctly validate character references, resolving some issues where a standalone ampersand `&` followed later in markup by a character reference (e.g. `&amp;`) could wrongly mark a block as being invalid. ([#13512](https://github.com/WordPress/gutenberg/pull/13512)) +- Block validation will now correctly validate character references, resolving some issues where a standalone ampersand `&` followed later in markup by a character reference (e.g. `&amp;`) could wrongly mark a block as being invalid. ([#13512](https://github.com/WordPress/gutenberg/pull/13512)) ## 6.0.5 (2019-01-03) @@ -49,11 +53,11 @@ ### Breaking Changes -- `isValidBlock` has been removed. Please use `isValidBlockContent` instead but keep in mind that the order of params has changed. +- `isValidBlock` has been removed. Please use `isValidBlockContent` instead but keep in mind that the order of params has changed. ### Bug Fix -- The block validator is more lenient toward equivalent encoding forms. +- The block validator is more lenient toward equivalent encoding forms. ## 5.3.1 (2018-11-12) @@ -61,14 +65,14 @@ ### New feature -- `getBlockAttributes`, `getBlockTransforms`, `getSaveContent`, `getSaveElement` and `isValidBlockContent` methods can now take also block's name as the first param ([#11490](https://github.com/WordPress/gutenberg/pull/11490)). Passing a block's type object continues to work as before. -- `registerBlockStyles` and `unregisterBlockStyles` can be triggered at any moment (before or after block registration). +- `getBlockAttributes`, `getBlockTransforms`, `getSaveContent`, `getSaveElement` and `isValidBlockContent` methods can now take also block's name as the first param ([#11490](https://github.com/WordPress/gutenberg/pull/11490)). Passing a block's type object continues to work as before. +- `registerBlockStyles` and `unregisterBlockStyles` can be triggered at any moment (before or after block registration). ## 5.2.0 (2018-11-09) -- Paste: Google Docs: fix nested formatting, sub, sup and del. -- Expose @wordpress/editor to Gutenberg mobile. -- Separate Paste Handler. +- Paste: Google Docs: fix nested formatting, sub, sup and del. +- Expose @wordpress/editor to Gutenberg mobile. +- Separate Paste Handler. ## 5.1.2 (2018-11-03) @@ -78,23 +82,23 @@ ### New features -- `isValidBlockContent` function has been added ([#10891](https://github.com/WordPress/gutenberg/pull/10891)). +- `isValidBlockContent` function has been added ([#10891](https://github.com/WordPress/gutenberg/pull/10891)). ### Deprecation -- `isValidBlock` function has been deprecated ([#10891](https://github.com/WordPress/gutenberg/pull/10891)). Use `isValidBlockContent` instead. +- `isValidBlock` function has been deprecated ([#10891](https://github.com/WordPress/gutenberg/pull/10891)). Use `isValidBlockContent` instead. ## 5.0.0 (2018-10-29) ### Breaking Changes -- Attribute type coercion has been removed. Omit the source to preserve type via serialized comment demarcation. -- `setUnknownTypeHandlerName` has been removed. Please use `setFreeformContentHandlerName` and `setUnregisteredTypeHandlerName` instead. -- `getUnknownTypeHandlerName` has been removed. Please use `getFreeformContentHandlerName` and `getUnregisteredTypeHandlerName` instead. +- Attribute type coercion has been removed. Omit the source to preserve type via serialized comment demarcation. +- `setUnknownTypeHandlerName` has been removed. Please use `setFreeformContentHandlerName` and `setUnregisteredTypeHandlerName` instead. +- `getUnknownTypeHandlerName` has been removed. Please use `getFreeformContentHandlerName` and `getUnregisteredTypeHandlerName` instead. ### New Feature -- Added a `unregisterBlockStyle()` function to remove a block style variation. +- Added a `unregisterBlockStyle()` function to remove a block style variation. ## 4.0.4 (2018-10-19) @@ -104,15 +108,15 @@ ### Breaking Changes -- `getDefaultBlockForPostFormat` has been removed. +- `getDefaultBlockForPostFormat` has been removed. ## 3.0.0 (2018-09-05) ### Breaking Changes -- The `isSharedBlock` function is removed. Use `isReusableBlock` instead. -- Change how required built-ins are polyfilled with Babel 7 ([#9171](https://github.com/WordPress/gutenberg/pull/9171)). If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. +- The `isSharedBlock` function is removed. Use `isReusableBlock` instead. +- Change how required built-ins are polyfilled with Babel 7 ([#9171](https://github.com/WordPress/gutenberg/pull/9171)). If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. ### Deprecations -- The `getDefaultBlockForPostFormat` function has been deprecated. +- The `getDefaultBlockForPostFormat` function has been deprecated. diff --git a/packages/blocks/README.md b/packages/blocks/README.md index 9116c3e5490c9e..c1f424fffd85a8 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -73,7 +73,7 @@ function random_image_enqueue_block_editor_assets() { wp_enqueue_script( 'random-image-block', plugins_url( 'block.js', __FILE__ ), - array( 'wp-blocks', 'wp-element' ) + array( 'wp-blocks', 'wp-element', 'wp-block-editor', ) ); } add_action( 'enqueue_block_editor_assets', 'random_image_enqueue_block_editor_assets' ); @@ -81,9 +81,10 @@ add_action( 'enqueue_block_editor_assets', 'random_image_enqueue_block_editor_as ```js // block.js -( function( blocks, element ) { +( function( blocks, element, blockEditor ) { var el = element.createElement, - source = blocks.source; + source = blocks.source, + useBlockProps = blockEditor.useBlockProps; function RandomImage( props ) { var src = 'http://lorempixel.com/400/200/' + props.category; @@ -95,6 +96,8 @@ add_action( 'enqueue_block_editor_assets', 'random_image_enqueue_block_editor_as } blocks.registerBlockType( 'myplugin/random-image', { + apiVersion: 2, + title: 'Random Image', icon: 'format-image', @@ -111,6 +114,7 @@ add_action( 'enqueue_block_editor_assets', 'random_image_enqueue_block_editor_as }, edit: function( props ) { + var blockProps = useBlockProps(); var category = props.attributes.category, children; @@ -134,7 +138,7 @@ add_action( 'enqueue_block_editor_assets', 'random_image_enqueue_block_editor_as ) ); - return el( 'form', { onSubmit: setCategory }, children ); + return el( 'form', Object.assing( blockProps, { onSubmit: setCategory } ), children ); }, save: function( props ) { @@ -143,7 +147,8 @@ add_action( 'enqueue_block_editor_assets', 'random_image_enqueue_block_editor_as } ); } )( window.wp.blocks, - window.wp.element + window.wp.element, + window.wp.blockEditor ); ``` @@ -740,6 +745,18 @@ _Parameters_ - _blockName_ `string`: Block name. +<a name="store" href="#store">#</a> **store** + +Store definition for the blocks namespace. + +_Related_ + +- <https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore> + +_Type_ + +- `Object` + <a name="switchToBlockType" href="#switchToBlockType">#</a> **switchToBlockType** Switch one or more blocks into one or more blocks of the new block type. diff --git a/packages/blocks/package.json b/packages/blocks/package.json index b79f7d62bce405..80417b159da2c0 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blocks", - "version": "6.24.1", + "version": "6.24.2", "description": "Block API for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -22,10 +22,10 @@ "module": "build-module/index.js", "react-native": "src/index", "sideEffects": [ - "!((src|build|build-module)/api/**)" + "{src,build,build-module}/{index.js,store/index.js}" ], "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/autop": "file:../autop", "@wordpress/blob": "file:../blob", "@wordpress/block-serialization-default-parser": "file:../block-serialization-default-parser", @@ -46,7 +46,7 @@ "showdown": "^1.9.1", "simple-html-tokenizer": "^0.5.7", "tinycolor2": "^1.4.1", - "uuid": "^7.0.2" + "uuid": "^8.3.0" }, "publishConfig": { "access": "public" diff --git a/packages/blocks/src/api/categories.js b/packages/blocks/src/api/categories.js index 6e89b489960b0e..1a0f5a319b277b 100644 --- a/packages/blocks/src/api/categories.js +++ b/packages/blocks/src/api/categories.js @@ -3,6 +3,11 @@ */ import { dispatch, select } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { store as blocksStore } from '../store'; + /** @typedef {import('../store/reducer').WPBlockCategory} WPBlockCategory */ /** @@ -11,7 +16,7 @@ import { dispatch, select } from '@wordpress/data'; * @return {WPBlockCategory[]} Block categories. */ export function getCategories() { - return select( 'core/blocks' ).getCategories(); + return select( blocksStore ).getCategories(); } /** @@ -20,7 +25,7 @@ export function getCategories() { * @param {WPBlockCategory[]} categories Block categories. */ export function setCategories( categories ) { - dispatch( 'core/blocks' ).setCategories( categories ); + dispatch( blocksStore ).setCategories( categories ); } /** @@ -31,5 +36,5 @@ export function setCategories( categories ) { * that should be updated. */ export function updateCategory( slug, category ) { - dispatch( 'core/blocks' ).updateCategory( slug, category ); + dispatch( blocksStore ).updateCategory( slug, category ); } diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index 18488d1db55f79..8612244ff09840 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -13,14 +13,68 @@ export const DEPRECATED_ENTRY_KEYS = [ ]; export const __EXPERIMENTAL_STYLE_PROPERTY = { - '--wp--style--color--link': [ 'color', 'link' ], - background: [ 'color', 'gradient' ], - backgroundColor: [ 'color', 'background' ], - color: [ 'color', 'text' ], - fontSize: [ 'typography', 'fontSize' ], - lineHeight: [ 'typography', 'lineHeight' ], - paddingBottom: [ 'spacing', 'padding', 'bottom' ], - paddingLeft: [ 'spacing', 'padding', 'left' ], - paddingRight: [ 'spacing', 'padding', 'right' ], - paddingTop: [ 'spacing', 'padding', 'top' ], + '--wp--style--color--link': { + value: [ 'color', 'link' ], + support: [ 'color', 'link' ], + }, + background: { + value: [ 'color', 'gradient' ], + support: [ 'color', 'gradients' ], + }, + backgroundColor: { + value: [ 'color', 'background' ], + support: [ 'color' ], + }, + borderRadius: { + value: [ 'border', 'radius' ], + support: [ '__experimentalBorder', 'radius' ], + }, + color: { + value: [ 'color', 'text' ], + support: [ 'color' ], + }, + fontFamily: { + value: [ 'typography', 'fontFamily' ], + support: [ '__experimentalFontFamily' ], + }, + fontSize: { + value: [ 'typography', 'fontSize' ], + support: [ 'fontSize' ], + }, + fontStyle: { + value: [ 'typography', 'fontStyle' ], + support: [ '__experimentalFontStyle' ], + }, + fontWeight: { + value: [ 'typography', 'fontWeight' ], + support: [ '__experimentalFontWeight' ], + }, + lineHeight: { + value: [ 'typography', 'lineHeight' ], + support: [ 'lineHeight' ], + }, + paddingBottom: { + value: [ 'spacing', 'padding', 'bottom' ], + support: [ 'spacing', 'padding' ], + }, + paddingLeft: { + value: [ 'spacing', 'padding', 'left' ], + support: [ 'spacing', 'padding' ], + }, + paddingRight: { + value: [ 'spacing', 'padding', 'right' ], + support: [ 'spacing', 'padding' ], + }, + paddingTop: { + value: [ 'spacing', 'padding', 'top' ], + support: [ 'spacing', 'padding' ], + }, + textDecoration: { + value: [ 'typography', 'textDecoration' ], + support: [ '__experimentalTextDecoration' ], + }, + textTransform: { + value: [ 'typography', 'textTransform' ], + support: [ '__experimentalTextTransform' ], + }, }; diff --git a/packages/blocks/src/api/raw-handling/test/utils.js b/packages/blocks/src/api/raw-handling/test/utils.js index 45e4d84c0c3352..d6a800e5383a26 100644 --- a/packages/blocks/src/api/raw-handling/test/utils.js +++ b/packages/blocks/src/api/raw-handling/test/utils.js @@ -7,12 +7,15 @@ import deepFreeze from 'deep-freeze'; * Internal dependencies */ import { getBlockContentSchema, isPlain } from '../utils'; +import { store as mockStore } from '../../../store'; +import { STORE_NAME as mockStoreName } from '../../../store/constants'; jest.mock( '@wordpress/data', () => { return { select: jest.fn( ( store ) => { switch ( store ) { - case 'core/blocks': { + case [ mockStoreName ]: + case mockStore: { return { hasBlockSupport: ( blockName, supports ) => { return ( @@ -24,6 +27,18 @@ jest.mock( '@wordpress/data', () => { } } } ), + combineReducers: () => { + const mock = jest.fn(); + return mock; + }, + createReduxStore: () => { + const mock = jest.fn(); + return mock; + }, + register: () => { + const mock = jest.fn(); + return mock; + }, }; } ); diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 9052c76faf6948..d628cdfae0306c 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -26,6 +26,7 @@ import { blockDefault } from '@wordpress/icons'; */ import { isValidIcon, normalizeIconObject } from './utils'; import { DEPRECATED_ENTRY_KEYS } from './constants'; +import { store as blocksStore } from '../store'; /** * An icon type definition. One of a Dashicon slug, an element, @@ -70,7 +71,7 @@ import { DEPRECATED_ENTRY_KEYS } from './constants'; /** * Named block variation scopes. * - * @typedef {'block'|'inserter'} WPBlockVariationScope + * @typedef {'block'|'inserter'|'transform'} WPBlockVariationScope */ /** @@ -191,7 +192,7 @@ export function registerBlockType( name, settings ) { ); return; } - if ( select( 'core/blocks' ).getBlockType( name ) ) { + if ( select( blocksStore ).getBlockType( name ) ) { console.error( 'Block "' + name + '" is already registered.' ); return; } @@ -242,7 +243,7 @@ export function registerBlockType( name, settings ) { if ( 'category' in settings && - ! some( select( 'core/blocks' ).getCategories(), { + ! some( select( blocksStore ).getCategories(), { slug: settings.category, } ) ) { @@ -274,7 +275,7 @@ export function registerBlockType( name, settings ) { return; } - dispatch( 'core/blocks' ).addBlockTypes( settings ); + dispatch( blocksStore ).addBlockTypes( settings ); return settings; } @@ -288,7 +289,7 @@ export function registerBlockType( name, settings ) { * @param {Object} [settings.icon] The icon to display in the block inserter. */ export function registerBlockCollection( namespace, { title, icon } ) { - dispatch( 'core/blocks' ).addBlockCollection( namespace, title, icon ); + dispatch( blocksStore ).addBlockCollection( namespace, title, icon ); } /** @@ -298,7 +299,7 @@ export function registerBlockCollection( namespace, { title, icon } ) { * */ export function unregisterBlockCollection( namespace ) { - dispatch( 'core/blocks' ).removeBlockCollection( namespace ); + dispatch( blocksStore ).removeBlockCollection( namespace ); } /** @@ -310,12 +311,12 @@ export function unregisterBlockCollection( namespace ) { * unregistered; otherwise `undefined`. */ export function unregisterBlockType( name ) { - const oldBlock = select( 'core/blocks' ).getBlockType( name ); + const oldBlock = select( blocksStore ).getBlockType( name ); if ( ! oldBlock ) { console.error( 'Block "' + name + '" is not registered.' ); return; } - dispatch( 'core/blocks' ).removeBlockTypes( name ); + dispatch( blocksStore ).removeBlockTypes( name ); return oldBlock; } @@ -325,7 +326,7 @@ export function unregisterBlockType( name ) { * @param {string} blockName Block name. */ export function setFreeformContentHandlerName( blockName ) { - dispatch( 'core/blocks' ).setFreeformFallbackBlockName( blockName ); + dispatch( blocksStore ).setFreeformFallbackBlockName( blockName ); } /** @@ -335,7 +336,7 @@ export function setFreeformContentHandlerName( blockName ) { * @return {?string} Block name. */ export function getFreeformContentHandlerName() { - return select( 'core/blocks' ).getFreeformFallbackBlockName(); + return select( blocksStore ).getFreeformFallbackBlockName(); } /** @@ -344,7 +345,7 @@ export function getFreeformContentHandlerName() { * @return {?string} Block name. */ export function getGroupingBlockName() { - return select( 'core/blocks' ).getGroupingBlockName(); + return select( blocksStore ).getGroupingBlockName(); } /** @@ -353,7 +354,7 @@ export function getGroupingBlockName() { * @param {string} blockName Block name. */ export function setUnregisteredTypeHandlerName( blockName ) { - dispatch( 'core/blocks' ).setUnregisteredFallbackBlockName( blockName ); + dispatch( blocksStore ).setUnregisteredFallbackBlockName( blockName ); } /** @@ -363,7 +364,7 @@ export function setUnregisteredTypeHandlerName( blockName ) { * @return {?string} Block name. */ export function getUnregisteredTypeHandlerName() { - return select( 'core/blocks' ).getUnregisteredFallbackBlockName(); + return select( blocksStore ).getUnregisteredFallbackBlockName(); } /** @@ -372,7 +373,7 @@ export function getUnregisteredTypeHandlerName() { * @param {string} name Block name. */ export function setDefaultBlockName( name ) { - dispatch( 'core/blocks' ).setDefaultBlockName( name ); + dispatch( blocksStore ).setDefaultBlockName( name ); } /** @@ -381,7 +382,7 @@ export function setDefaultBlockName( name ) { * @param {string} name Block name. */ export function setGroupingBlockName( name ) { - dispatch( 'core/blocks' ).setGroupingBlockName( name ); + dispatch( blocksStore ).setGroupingBlockName( name ); } /** @@ -390,7 +391,7 @@ export function setGroupingBlockName( name ) { * @return {?string} Block name. */ export function getDefaultBlockName() { - return select( 'core/blocks' ).getDefaultBlockName(); + return select( blocksStore ).getDefaultBlockName(); } /** @@ -401,7 +402,7 @@ export function getDefaultBlockName() { * @return {?Object} Block type. */ export function getBlockType( name ) { - return select( 'core/blocks' ).getBlockType( name ); + return select( blocksStore ).getBlockType( name ); } /** @@ -410,7 +411,7 @@ export function getBlockType( name ) { * @return {Array} Block settings. */ export function getBlockTypes() { - return select( 'core/blocks' ).getBlockTypes(); + return select( blocksStore ).getBlockTypes(); } /** @@ -424,7 +425,7 @@ export function getBlockTypes() { * @return {?*} Block support value */ export function getBlockSupport( nameOrType, feature, defaultSupports ) { - return select( 'core/blocks' ).getBlockSupport( + return select( blocksStore ).getBlockSupport( nameOrType, feature, defaultSupports @@ -442,7 +443,7 @@ export function getBlockSupport( nameOrType, feature, defaultSupports ) { * @return {boolean} Whether block supports feature. */ export function hasBlockSupport( nameOrType, feature, defaultSupports ) { - return select( 'core/blocks' ).hasBlockSupport( + return select( blocksStore ).hasBlockSupport( nameOrType, feature, defaultSupports @@ -470,7 +471,7 @@ export function isReusableBlock( blockOrType ) { * @return {Array} Array of child block names. */ export const getChildBlockNames = ( blockName ) => { - return select( 'core/blocks' ).getChildBlockNames( blockName ); + return select( blocksStore ).getChildBlockNames( blockName ); }; /** @@ -481,7 +482,7 @@ export const getChildBlockNames = ( blockName ) => { * @return {boolean} True if a block contains child blocks and false otherwise. */ export const hasChildBlocks = ( blockName ) => { - return select( 'core/blocks' ).hasChildBlocks( blockName ); + return select( blocksStore ).hasChildBlocks( blockName ); }; /** @@ -493,9 +494,7 @@ export const hasChildBlocks = ( blockName ) => { * and false otherwise. */ export const hasChildBlocksWithInserterSupport = ( blockName ) => { - return select( 'core/blocks' ).hasChildBlocksWithInserterSupport( - blockName - ); + return select( blocksStore ).hasChildBlocksWithInserterSupport( blockName ); }; /** @@ -505,7 +504,7 @@ export const hasChildBlocksWithInserterSupport = ( blockName ) => { * @param {Object} styleVariation Object containing `name` which is the class name applied to the block and `label` which identifies the variation to the user. */ export const registerBlockStyle = ( blockName, styleVariation ) => { - dispatch( 'core/blocks' ).addBlockStyles( blockName, styleVariation ); + dispatch( blocksStore ).addBlockStyles( blockName, styleVariation ); }; /** @@ -515,10 +514,7 @@ export const registerBlockStyle = ( blockName, styleVariation ) => { * @param {string} styleVariationName Name of class applied to the block. */ export const unregisterBlockStyle = ( blockName, styleVariationName ) => { - dispatch( 'core/blocks' ).removeBlockStyles( - blockName, - styleVariationName - ); + dispatch( blocksStore ).removeBlockStyles( blockName, styleVariationName ); }; /** @@ -530,7 +526,7 @@ export const unregisterBlockStyle = ( blockName, styleVariationName ) => { * @return {(WPBlockVariation[]|void)} Block variations. */ export const getBlockVariations = ( blockName, scope ) => { - return select( 'core/blocks' ).getBlockVariations( blockName, scope ); + return select( blocksStore ).getBlockVariations( blockName, scope ); }; /** @@ -540,7 +536,7 @@ export const getBlockVariations = ( blockName, scope ) => { * @param {WPBlockVariation} variation Object describing a block variation. */ export const registerBlockVariation = ( blockName, variation ) => { - dispatch( 'core/blocks' ).addBlockVariations( blockName, variation ); + dispatch( blocksStore ).addBlockVariations( blockName, variation ); }; /** @@ -550,5 +546,5 @@ export const registerBlockVariation = ( blockName, variation ) => { * @param {string} variationName Name of the variation defined for the block. */ export const unregisterBlockVariation = ( blockName, variationName ) => { - dispatch( 'core/blocks' ).removeBlockVariations( blockName, variationName ); + dispatch( blocksStore ).removeBlockVariations( blockName, variationName ); }; diff --git a/packages/blocks/src/api/serializer.js b/packages/blocks/src/api/serializer.js index 2b081bf471fbb4..86b0147beea13a 100644 --- a/packages/blocks/src/api/serializer.js +++ b/packages/blocks/src/api/serializer.js @@ -17,6 +17,7 @@ import { getBlockType, getFreeformContentHandlerName, getUnregisteredTypeHandlerName, + hasBlockSupport, } from './registration'; import { normalizeBlockType } from './utils'; import BlockContentProvider from '../block-content-provider'; @@ -116,10 +117,14 @@ export function getSaveElement( let element = save( { attributes, innerBlocks } ); + const hasLightBlockWrapper = + blockType.apiVersion > 1 || + hasBlockSupport( blockType, 'lightBlockWrapper', false ); + if ( isObject( element ) && hasFilter( 'blocks.getSaveContent.extraProps' ) && - ! blockType.apiVersion + ! hasLightBlockWrapper ) { /** * Filters the props applied to the block save result element. diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index 9a7b5f41cad72d..13e43b4a52b10d 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -37,6 +37,7 @@ import { unstable__bootstrapServerSideBlockDefinitions, // eslint-disable-line camelcase } from '../registration'; import { DEPRECATED_ENTRY_KEYS } from '../constants'; +import { store as blocksStore } from '../../store'; describe( 'blocks', () => { const defaultBlockSettings = { @@ -732,7 +733,7 @@ describe( 'blocks', () => { it( 'creates a new block collection', () => { registerBlockCollection( 'core', { title: 'Core' } ); - expect( select( 'core/blocks' ).getCollections() ).toEqual( { + expect( select( blocksStore ).getCollections() ).toEqual( { core: { title: 'Core', icon: undefined }, } ); } ); @@ -744,7 +745,7 @@ describe( 'blocks', () => { registerBlockCollection( 'core2', { title: 'Core2' } ); unregisterBlockCollection( 'core' ); - expect( select( 'core/blocks' ).getCollections() ).toEqual( { + expect( select( blocksStore ).getCollections() ).toEqual( { core2: { title: 'Core2', icon: undefined }, } ); } ); diff --git a/packages/blocks/src/index.js b/packages/blocks/src/index.js index 579665d14b8538..0af3f5e0ae720d 100644 --- a/packages/blocks/src/index.js +++ b/packages/blocks/src/index.js @@ -8,10 +8,6 @@ // Blocks are inferred from the HTML source of a post through a parsing mechanism // and then stored as objects in state, from which it is then rendered for editing. -/** - * Internal dependencies - */ -import './store'; - +export { store } from './store'; export * from './api'; export { withBlockContentContext } from './block-content-provider'; diff --git a/packages/blocks/src/store/constants.js b/packages/blocks/src/store/constants.js new file mode 100644 index 00000000000000..7dda6b11d0254e --- /dev/null +++ b/packages/blocks/src/store/constants.js @@ -0,0 +1 @@ +export const STORE_NAME = 'core/blocks'; diff --git a/packages/blocks/src/store/index.js b/packages/blocks/src/store/index.js index 4ba62857cb05cf..f88fe4c423fa4a 100644 --- a/packages/blocks/src/store/index.js +++ b/packages/blocks/src/store/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { registerStore } from '@wordpress/data'; +import { createReduxStore, register } from '@wordpress/data'; /** * Internal dependencies @@ -9,5 +9,19 @@ import { registerStore } from '@wordpress/data'; import reducer from './reducer'; import * as selectors from './selectors'; import * as actions from './actions'; +import { STORE_NAME } from './constants'; -registerStore( 'core/blocks', { reducer, selectors, actions } ); +/** + * Store definition for the blocks namespace. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore + * + * @type {Object} + */ +export const store = createReduxStore( STORE_NAME, { + reducer, + selectors, + actions, +} ); + +register( store ); diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index 21e88311b522ff..5513ff4e4c4885 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -90,7 +90,8 @@ export function getBlockVariations( state, blockName, scope ) { return variations; } return variations.filter( ( variation ) => { - return ! variation.scope || variation.scope.includes( scope ); + // For backward compatibility reasons, variation's scope defaults to `block` and `inserter` when not set. + return ( variation.scope || [ 'block', 'inserter' ] ).includes( scope ); } ); } diff --git a/packages/blocks/src/store/test/selectors.js b/packages/blocks/src/store/test/selectors.js index fde5d922536227..328293a898fcd3 100644 --- a/packages/blocks/src/store/test/selectors.js +++ b/packages/blocks/src/store/test/selectors.js @@ -11,6 +11,7 @@ import deepFreeze from 'deep-freeze'; import { getBlockSupport, getChildBlockNames, + getBlockVariations, getDefaultBlockVariation, getGroupingBlockName, isMatchingSearchTerm, @@ -220,7 +221,7 @@ describe( 'selectors', () => { } ); } ); - describe( 'getDefaultBlockVariation', () => { + describe( 'Testing block variations selectors', () => { const blockName = 'block/name'; const createBlockVariationsState = ( variations ) => { return deepFreeze( { @@ -238,55 +239,94 @@ describe( 'selectors', () => { const thirdBlockVariation = { name: 'third-block-variation', }; + describe( 'getBlockVariations', () => { + it( 'should return undefined if no variations exists', () => { + expect( + getBlockVariations( { blockVariations: {} }, blockName ) + ).toBeUndefined(); + } ); + it( 'should return all variations if scope is not provided', () => { + const variations = [ + firstBlockVariation, + secondBlockVariation, + ]; + const state = createBlockVariationsState( variations ); + expect( getBlockVariations( state, blockName ) ).toEqual( + variations + ); + } ); + it( 'should return variations with scope not set at all or explicitly set', () => { + const variations = [ + { ...firstBlockVariation, scope: [ 'inserter' ] }, + { name: 'only-block', scope: [ 'block' ] }, + { + name: 'multiple-scopes-with-block', + scope: [ 'transform', 'block' ], + }, + { name: 'no-scope' }, + ]; + const state = createBlockVariationsState( variations ); + const result = getBlockVariations( state, blockName, 'block' ); + expect( result ).toHaveLength( 3 ); + expect( result.map( ( { name } ) => name ) ).toEqual( + expect.arrayContaining( [ + 'only-block', + 'multiple-scopes-with-block', + 'no-scope', + ] ) + ); + } ); + } ); + describe( 'getDefaultBlockVariation', () => { + it( 'should return the default variation when set', () => { + const defaultBlockVariation = { + ...secondBlockVariation, + isDefault: true, + }; + const state = createBlockVariationsState( [ + firstBlockVariation, + defaultBlockVariation, + thirdBlockVariation, + ] ); - it( 'should return the default variation when set', () => { - const defaultBlockVariation = { - ...secondBlockVariation, - isDefault: true, - }; - const state = createBlockVariationsState( [ - firstBlockVariation, - defaultBlockVariation, - thirdBlockVariation, - ] ); - - const result = getDefaultBlockVariation( state, blockName ); + const result = getDefaultBlockVariation( state, blockName ); - expect( result ).toEqual( defaultBlockVariation ); - } ); + expect( result ).toEqual( defaultBlockVariation ); + } ); - it( 'should return the last variation when multiple default variations added', () => { - const defaultBlockVariation = { - ...thirdBlockVariation, - isDefault: true, - }; - const state = createBlockVariationsState( [ - { - ...firstBlockVariation, - isDefault: true, - }, - { - ...secondBlockVariation, + it( 'should return the last variation when multiple default variations added', () => { + const defaultBlockVariation = { + ...thirdBlockVariation, isDefault: true, - }, - defaultBlockVariation, - ] ); + }; + const state = createBlockVariationsState( [ + { + ...firstBlockVariation, + isDefault: true, + }, + { + ...secondBlockVariation, + isDefault: true, + }, + defaultBlockVariation, + ] ); - const result = getDefaultBlockVariation( state, blockName ); + const result = getDefaultBlockVariation( state, blockName ); - expect( result ).toEqual( defaultBlockVariation ); - } ); + expect( result ).toEqual( defaultBlockVariation ); + } ); - it( 'should return the first variation when no default variation set', () => { - const state = createBlockVariationsState( [ - firstBlockVariation, - secondBlockVariation, - thirdBlockVariation, - ] ); + it( 'should return the first variation when no default variation set', () => { + const state = createBlockVariationsState( [ + firstBlockVariation, + secondBlockVariation, + thirdBlockVariation, + ] ); - const result = getDefaultBlockVariation( state, blockName ); + const result = getDefaultBlockVariation( state, blockName ); - expect( result ).toEqual( firstBlockVariation ); + expect( result ).toEqual( firstBlockVariation ); + } ); } ); } ); diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index edebec183b48c6..77838c82cc3e96 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,7 +2,20 @@ ## Unreleased -- Introduce `Navigation` component as `__experimentalNavigation` for displaying a hierarchy of items. +### Breaking Change + +- Introduce support for other units and advanced CSS properties on `FontSizePicker`. Provided the value passed to the `FontSizePicker` is a string or one of the size options passed is a string, onChange will start to be called with a string value instead of a number. On WordPress usage, font size options are now automatically converted to strings with the default "px" unit added. + +## 10.1.0 (2020-09-03) + +### New Feature + +- Add `ToolbarItem` component. +- Support `label` prop on the `Toolbar` component. + +### Deprecations + +- Deprecate the `Toolbar` component when used without the `label` prop. `ToolbarGroup` should be used instead. ## 10.0.0 (2020-07-07) diff --git a/packages/components/package.json b/packages/components/package.json index ad6f16f9d8d263..f4d806cde02dd2 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "11.1.1", + "version": "11.1.2", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -22,10 +22,11 @@ "module": "build-module/index.js", "react-native": "src/index", "sideEffects": [ - "build-style/**" + "build-style/**", + "src/**/*.scss" ], "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@emotion/core": "^10.0.22", "@emotion/css": "^10.0.22", "@emotion/native": "^10.0.22", @@ -60,7 +61,7 @@ "reakit": "^1.1.0", "rememo": "^3.0.0", "tinycolor2": "^1.4.1", - "uuid": "^7.0.2" + "uuid": "^8.3.0" }, "publishConfig": { "access": "public" diff --git a/packages/components/src/angle-picker-control/angle-circle.js b/packages/components/src/angle-picker-control/angle-circle.js index f2a761284d96e2..775a697ca25aac 100644 --- a/packages/components/src/angle-picker-control/angle-circle.js +++ b/packages/components/src/angle-picker-control/angle-circle.js @@ -28,11 +28,12 @@ function AngleCircle( { value, onChange, ...props } ) { const changeAngleToPosition = ( event ) => { const { x: centerX, y: centerY } = angleCircleCenter.current; + const { ownerDocument } = angleCircleRef.current; // Prevent (drag) mouse events from selecting and accidentally // triggering actions from other elements. event.preventDefault(); // Ensure the input isn't focused as preventDefault would leave it - document.activeElement.blur(); + ownerDocument.activeElement.blur(); onChange( getAngle( centerX, centerY, event.clientX, event.clientY ) ); }; diff --git a/packages/components/src/angle-picker-control/index.js b/packages/components/src/angle-picker-control/index.js index 9d6e52c5b7e845..3b089f1ccefcc7 100644 --- a/packages/components/src/angle-picker-control/index.js +++ b/packages/components/src/angle-picker-control/index.js @@ -7,25 +7,24 @@ import classnames from 'classnames'; * WordPress dependencies */ import { useInstanceId } from '@wordpress/compose'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import BaseControl from '../base-control'; -import { FlexBlock } from '../flex'; +import { FlexBlock, FlexItem } from '../flex'; import NumberControl from '../number-control'; import AngleCircle from './angle-circle'; -import { - Root, - NumberControlWrapper, -} from './styles/angle-picker-control-styles'; +import { Root } from './styles/angle-picker-control-styles'; export default function AnglePickerControl( { className, + hideLabelFromVision, id: idProp, - value, + label = __( 'Angle' ), onChange, - label, + value, ...props } ) { const instanceId = useInstanceId( @@ -45,12 +44,13 @@ export default function AnglePickerControl( { return ( <BaseControl className={ classes } + hideLabelFromVision={ hideLabelFromVision } id={ id } label={ label } { ...props } > - <Root gap={ 3 }> - <NumberControlWrapper> + <Root> + <FlexBlock> <NumberControl className="components-angle-picker-control__input-field" id={ id } @@ -60,14 +60,14 @@ export default function AnglePickerControl( { step="1" value={ value } /> - </NumberControlWrapper> - <FlexBlock> + </FlexBlock> + <FlexItem> <AngleCircle aria-hidden="true" value={ value } onChange={ onChange } /> - </FlexBlock> + </FlexItem> </Root> </BaseControl> ); diff --git a/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js b/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js index f63f9bc6d1b00a..f025cddb77907c 100644 --- a/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js +++ b/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js @@ -6,7 +6,7 @@ import styled from '@emotion/styled'; /** * Internal dependencies */ -import { Flex, FlexItem } from '../../flex'; +import { Flex } from '../../flex'; import { color } from '../../utils/style-mixins'; const CIRCLE_SIZE = 30; @@ -15,10 +15,6 @@ export const Root = styled( Flex )` max-width: 200px; `; -export const NumberControlWrapper = styled( FlexItem )` - width: 80px; -`; - export const CircleRoot = styled.div` border-radius: 50%; border: 1px solid ${ color( 'ui.borderLight' ) }; diff --git a/packages/components/src/animate/index.js b/packages/components/src/animate/index.js index 2eeed8e7be1306..95fba645fd1782 100644 --- a/packages/components/src/animate/index.js +++ b/packages/components/src/animate/index.js @@ -3,11 +3,37 @@ */ import classnames from 'classnames'; +/** + * @typedef {'top' | 'top left' | 'top right' | 'middle' | 'middle left' | 'middle right' | 'bottom' | 'bottom left' | 'bottom right'} AppearOrigin + * @typedef {'left' | 'right'} SlideInOrigin + * @typedef {{ type: 'appear'; origin?: AppearOrigin }} AppearOptions + * @typedef {{ type: 'slide-in'; origin?: SlideInOrigin }} SlideInOptions + * @typedef {{ type: 'loading'; }} LoadingOptions + * @typedef {AppearOptions | SlideInOptions | LoadingOptions} GetAnimateOptions + */ + +/* eslint-disable jsdoc/valid-types */ +/** + * @param {GetAnimateOptions['type']} type The animation type + * @return {'top' | 'left'} Default origin + */ function getDefaultOrigin( type ) { return type === 'appear' ? 'top' : 'left'; } +/* eslint-enable jsdoc/valid-types */ + +/** + * @param {GetAnimateOptions} options + * + * @return {string | void} ClassName that applies the animations + */ +export function getAnimateClassName( options ) { + if ( options.type === 'loading' ) { + return classnames( 'components-animate__loading' ); + } + + const { type, origin = getDefaultOrigin( type ) } = options; -export function useAnimate( { type, origin = getDefaultOrigin( type ) } ) { if ( type === 'appear' ) { const [ yAxis, xAxis = 'center' ] = origin.split( ' ' ); return classnames( 'components-animate__appear', { @@ -22,14 +48,11 @@ export function useAnimate( { type, origin = getDefaultOrigin( type ) } ) { 'is-from-' + origin ); } - - if ( type === 'loading' ) { - return classnames( 'components-animate__loading' ); - } } +// @ts-ignore Reason: Planned for deprecation export default function Animate( { type, options = {}, children } ) { return children( { - className: useAnimate( { type, ...options } ), + className: getAnimateClassName( { type, ...options } ), } ); } diff --git a/packages/components/src/autocomplete/index.js b/packages/components/src/autocomplete/index.js index c61ccc4f634bc4..e2548413b74d53 100644 --- a/packages/components/src/autocomplete/index.js +++ b/packages/components/src/autocomplete/index.js @@ -22,6 +22,7 @@ import { insert, isCollapsed, getTextContent, + useAnchorRef, } from '@wordpress/rich-text'; /** @@ -136,11 +137,6 @@ function filterOptions( search, options = [], maxResults = 10 ) { return filtered; } -function getRange() { - const selection = window.getSelection(); - return selection.rangeCount ? selection.getRangeAt( 0 ) : null; -} - const getAutoCompleterUI = ( autocompleter ) => { const useItems = autocompleter.useItems ? autocompleter.useItems @@ -227,8 +223,12 @@ const getAutoCompleterUI = ( autocompleter ) => { onChangeOptions, onSelect, onReset, + value, + contentRef, } ) { const [ items ] = useItems( filterValue ); + const anchorRef = useAnchorRef( { ref: contentRef, value } ); + useLayoutEffect( () => { onChangeOptions( items ); }, [ items ] ); @@ -243,7 +243,7 @@ const getAutoCompleterUI = ( autocompleter ) => { onClose={ onReset } position="top right" className="components-autocomplete__popover" - anchorRef={ getRange() } + anchorRef={ anchorRef } > <div id={ listBoxId } @@ -285,6 +285,7 @@ function Autocomplete( { onReplace, completers, debouncedSpeak, + contentRef, } ) { const instanceId = useInstanceId( Autocomplete ); const [ selectedIndex, setSelectedIndex ] = useState( 0 ); @@ -501,6 +502,8 @@ function Autocomplete( { selectedIndex={ selectedIndex } onChangeOptions={ onChangeOptions } onSelect={ select } + value={ record } + contentRef={ contentRef } /> ) } </> diff --git a/packages/components/src/autocomplete/style.scss b/packages/components/src/autocomplete/style.scss index 2b4a585dc50951..3bef5a13a47b6e 100644 --- a/packages/components/src/autocomplete/style.scss +++ b/packages/components/src/autocomplete/style.scss @@ -1,5 +1,6 @@ .components-autocomplete__popover .components-popover__content > div { padding: $grid-unit-20; + min-width: 220px; } .components-autocomplete__result.components-button { diff --git a/packages/components/src/base-control/index.js b/packages/components/src/base-control/index.js index cf43dfe196c6f3..912a547591a405 100644 --- a/packages/components/src/base-control/index.js +++ b/packages/components/src/base-control/index.js @@ -14,6 +14,26 @@ import { StyledHelp, } from './styles/base-control-styles'; +/** + * @typedef Props + * @property {string} id The id of the element to which labels and help text are being generated. + * That element should be passed as a child. + * @property {import('react').ReactNode} help If this property is added, a help text will be + * generated using help property as the content. + * @property {import('react').ReactNode} label If this property is added, a label will be generated + * using label property as the content. + * @property {boolean} [hideLabelFromVision] If true, the label will only be visible to screen readers. + * @property {string} [className] The class that will be added with "components-base-control" to the + * classes of the wrapper div. If no className is passed only + * components-base-control is used. + * @property {import('react').ReactNode} [children] The content to be displayed within + * the BaseControl. + */ + +/** + * @param {Props} props + * @return {JSX.Element} Element + */ function BaseControl( { id, label, @@ -64,6 +84,16 @@ function BaseControl( { ); } +/** + * @typedef VisualLabelProps + * @property {string} [className] Class name + * @property {import('react').ReactNode} [children] Children + */ + +/** + * @param {VisualLabelProps} Props + * @return {JSX.Element} Element + */ BaseControl.VisualLabel = ( { className, children } ) => { className = classnames( 'components-base-control__label', className ); return <span className={ className }>{ children }</span>; diff --git a/packages/components/src/button/README.md b/packages/components/src/button/README.md index 899f3ebade6e0b..e28a5aca774f27 100644 --- a/packages/components/src/button/README.md +++ b/packages/components/src/button/README.md @@ -239,6 +239,14 @@ If provided with `icon`, sets the icon size. - Required: No - Default: `20 when a Dashicon is rendered, 24 for all other icons.` +#### iconPosition + +If provided with `icon`, sets the position of icon relative to the `text`. Available options are `left|right`. + +- Type: `string` +- Required: No +- Default: `left` + #### showTooltip If provided, renders a [Tooltip](/packages/components/src/tooltip/README.md) component for the button. diff --git a/packages/components/src/button/index.js b/packages/components/src/button/index.js index a5b3f8eb8bd2e2..698c52e453b8a8 100644 --- a/packages/components/src/button/index.js +++ b/packages/components/src/button/index.js @@ -34,12 +34,14 @@ export function Button( props, ref ) { className, disabled, icon, + iconPosition = 'left', iconSize, showTooltip, tooltipPosition, shortcut, label, children, + text, __experimentalIsFocusable: isFocusable, ...additionalProps } = props; @@ -110,7 +112,13 @@ export function Button( props, ref ) { aria-label={ additionalProps[ 'aria-label' ] || label } ref={ ref } > - { icon && <Icon icon={ icon } size={ iconSize } /> } + { icon && iconPosition === 'left' && ( + <Icon icon={ icon } size={ iconSize } /> + ) } + { text && <>{ text }</> } + { icon && iconPosition === 'right' && ( + <Icon icon={ icon } size={ iconSize } /> + ) } { children } </Tag> ); diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss index 2c90e5ab1e1a16..f7cc62fc5efc1a 100644 --- a/packages/components/src/button/style.scss +++ b/packages/components/src/button/style.scss @@ -32,7 +32,7 @@ box-shadow: 0 0 0 $border-width-focus var(--wp-admin-theme-color); // Windows High Contrast mode will show this outline, but not the box-shadow. - outline: 1px solid transparent; + outline: 3px solid transparent; } /** @@ -46,6 +46,9 @@ text-decoration: none; text-shadow: none; + // Show the boundary of the button, in High Contrast Mode. + outline: 1px solid transparent; + &:hover:not(:disabled) { background: var(--wp-admin-theme-color-darker-10); color: $white; @@ -59,9 +62,6 @@ &:focus:not(:disabled) { box-shadow: inset 0 0 0 1px $white, 0 0 0 $border-width-focus var(--wp-admin-theme-color); - - // Windows High Contrast mode will show this outline, but not the box-shadow. - outline: 1px solid transparent; } &:disabled, @@ -73,6 +73,7 @@ background: var(--wp-admin-theme-color); border-color: var(--wp-admin-theme-color); opacity: 1; + outline: none; &:focus:enabled { box-shadow: @@ -90,10 +91,10 @@ /* stylelint-disable */ background-image: linear-gradient( -45deg, - var(--wp-admin-theme-color) 28%, - var(--wp-admin-theme-color-darker-20) 28%, - var(--wp-admin-theme-color-darker-20) 72%, - var(--wp-admin-theme-color) 72% + var(--wp-admin-theme-color) 33%, + var(--wp-admin-theme-color-darker-20) 33%, + var(--wp-admin-theme-color-darker-20) 70%, + var(--wp-admin-theme-color) 70% ); /* stylelint-enable */ border-color: var(--wp-admin-theme-color); @@ -106,6 +107,9 @@ &.is-secondary, &.is-tertiary { + // Show the boundary of the button, in High Contrast Mode. + outline: 1px solid transparent; + &:active:not(:disabled) { background: $gray-300; color: var(--wp-admin-theme-color-darker-10); @@ -125,6 +129,7 @@ transform: none; opacity: 1; box-shadow: none; + outline: none; } } @@ -154,9 +159,6 @@ display: inline-block; flex: 0 0 auto; } - - // Windows High Contrast mode. - outline: 1px dotted transparent; } /** @@ -253,10 +255,10 @@ /* stylelint-disable */ background-image: linear-gradient( -45deg, - darken($white, 2%) 28%, - darken($white, 12%) 28%, - darken($white, 12%) 72%, - darken($white, 2%) 72% + darken($white, 2%) 33%, + darken($white, 12%) 33%, + darken($white, 12%) 70%, + darken($white, 2%) 70% ); /* stylelint-enable */ } diff --git a/packages/components/src/circular-option-picker/style.scss b/packages/components/src/circular-option-picker/style.scss index 0e23e51d24189a..353a432dda6639 100644 --- a/packages/components/src/circular-option-picker/style.scss +++ b/packages/components/src/circular-option-picker/style.scss @@ -4,16 +4,17 @@ $color-palette-circle-spacing: 12px; .components-circular-option-picker { display: inline-block; width: 100%; - margin-right: -10px; + min-width: 188px; .components-circular-option-picker__custom-clear-wrapper { display: flex; justify-content: flex-end; } - // Account for scrollbar or no scrollbar. + // Effectively negates the end swatch spacing to keep the swatches + // from wrapping before necessary. .components-circular-option-picker__swatches { - margin-right: -$grid-unit-20; + margin-right: -$color-palette-circle-spacing; } } @@ -21,7 +22,7 @@ $color-palette-circle-spacing: 12px; display: inline-block; height: $color-palette-circle-size; width: $color-palette-circle-size; - margin-right: $color-palette-circle-spacing + $grid-unit-05; + margin-right: $color-palette-circle-spacing; margin-bottom: $color-palette-circle-spacing; vertical-align: top; transform: scale(1); diff --git a/packages/components/src/color-edit/index.js b/packages/components/src/color-edit/index.js index fc38c443f5a915..31b49ad1454b33 100644 --- a/packages/components/src/color-edit/index.js +++ b/packages/components/src/color-edit/index.js @@ -41,6 +41,7 @@ function ColorOption( { isEditingNameOnMount = false, isEditingColorOnMount = false, onCancel, + immutableColorSlugs = [], } ) { const [ isHover, setIsHover ] = useState( false ); const [ isFocused, setIsFocused ] = useState( false ); @@ -52,7 +53,8 @@ function ColorOption( { ); const isShowingControls = - isHover || isFocused || isEditingName || isShowingAdvancedPanel; + ( isHover || isFocused || isEditingName || isShowingAdvancedPanel ) && + ! immutableColorSlugs.includes( slug ); return ( <div @@ -222,7 +224,13 @@ function ColorInserter( { onInsert, onCancel } ) { ); } -export default function ColorEdit( { colors, onChange, emptyUI } ) { +export default function ColorEdit( { + colors, + onChange, + emptyUI, + immutableColorSlugs, + canReset = true, +} ) { const [ isInsertingColor, setIsInsertingColor ] = useState( false ); return ( <BaseControl> @@ -254,6 +262,7 @@ export default function ColorEdit( { colors, onChange, emptyUI } ) { color={ color.color } name={ color.name } slug={ color.slug } + immutableColorSlugs={ immutableColorSlugs } onChange={ ( newColor ) => { onChange( colors.map( @@ -302,6 +311,16 @@ export default function ColorEdit( { colors, onChange, emptyUI } ) { ) } { ! isInsertingColor && isEmpty( colors ) && emptyUI } </div> + { !! canReset && ( + <Button + isSmall + isSecondary + className="components-color-edit__reset-button" + onClick={ () => onChange() } + > + { __( 'Reset' ) } + </Button> + ) } </fieldset> </BaseControl> ); diff --git a/packages/components/src/color-edit/style.scss b/packages/components/src/color-edit/style.scss index 44d4dff011e556..ef050b1a6fa4a7 100644 --- a/packages/components/src/color-edit/style.scss +++ b/packages/components/src/color-edit/style.scss @@ -1,7 +1,8 @@ .components-color-edit__color-option-main-area { display: flex; align-items: center; - .components-circular-option-picker__option-wrapper { + div.components-circular-option-picker__option-wrapper { + display: block; margin: $grid-unit-10; } } @@ -40,3 +41,7 @@ .components-color-edit__slug-input { margin-left: $grid-unit-10; } + +.components-color-edit__reset-button { + float: right; +} diff --git a/packages/components/src/color-palette/index.native.js b/packages/components/src/color-palette/index.native.js index cc75a22c67d3c8..8df4889acd83b0 100644 --- a/packages/components/src/color-palette/index.native.js +++ b/packages/components/src/color-palette/index.native.js @@ -46,9 +46,9 @@ function ColorPalette( { customIndicatorWrapperStyles, } ) { const customSwatchGradients = [ - 'linear-gradient(120deg, rgba(255,0,0,.8), 0%, rgba(255,255,255,1) 70.71%)', - 'linear-gradient(240deg, rgba(0,255,0,.8), 0%, rgba(0,255,0,0) 70.71%)', - 'linear-gradient(360deg, rgba(0,0,255,.8), 0%, rgba(0,0,255,0) 70.71%)', + 'linear-gradient(120deg, rgba(255,0,0,.8) 0%, rgba(255,255,255,1) 70.71%)', + 'linear-gradient(240deg, rgba(0,255,0,.8) 0%, rgba(0,255,0,0) 70.71%)', + 'linear-gradient(360deg, rgba(0,0,255,.8) 0%, rgba(0,0,255,0) 70.71%)', ]; const scrollViewRef = useRef(); diff --git a/packages/components/src/combobox-control/README.md b/packages/components/src/combobox-control/README.md index b0be706b7dc2b2..d2868ad066db78 100644 --- a/packages/components/src/combobox-control/README.md +++ b/packages/components/src/combobox-control/README.md @@ -1,6 +1,6 @@ # ComboboxControl -`ComboboxControl` is an enhanced version of a [`SelectControl`](/packages/components/src/select-control/readme.md), with the addition of being able to search for options using a search input. +`ComboboxControl` is an enhanced version of a [`SelectControl`](/packages/components/src/select-control/README.md), with the addition of being able to search for options using a search input. ## Table of contents @@ -10,7 +10,7 @@ ## Design guidelines -These are the same as [the ones for `SelectControl`s](/packages/components/src/select-control/readme.md#design-guidelines), but this component is better suited for when there are too many items to scroll through or load at once so you need to filter them based on user input. +These are the same as [the ones for `SelectControl`s](/packages/components/src/select-control/README.md#design-guidelines), but this component is better suited for when there are too many items to scroll through or load at once so you need to filter them based on user input. ## Development guidelines diff --git a/packages/components/src/combobox-control/index.js b/packages/components/src/combobox-control/index.js index 9cf667f6a09387..d1434c1787cfd6 100644 --- a/packages/components/src/combobox-control/index.js +++ b/packages/components/src/combobox-control/index.js @@ -7,7 +7,13 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __, _n, sprintf } from '@wordpress/i18n'; -import { useState, useMemo, useRef, useEffect } from '@wordpress/element'; +import { + Component, + useState, + useMemo, + useRef, + useEffect, +} from '@wordpress/element'; import { useInstanceId } from '@wordpress/compose'; import { ENTER, UP, DOWN, ESCAPE } from '@wordpress/keycodes'; import { speak } from '@wordpress/a11y'; @@ -21,6 +27,19 @@ import SuggestionsList from '../form-token-field/suggestions-list'; import BaseControl from '../base-control'; import Button from '../button'; import { Flex, FlexBlock, FlexItem } from '../flex'; +import withFocusOutside from '../higher-order/with-focus-outside'; + +const DetectOutside = withFocusOutside( + class extends Component { + handleFocusOutside( event ) { + this.props.onFocusOutside( event ); + } + + render() { + return this.props.children; + } + } +); function ComboboxControl( { value, @@ -119,7 +138,7 @@ function ComboboxControl( { setInputValue( '' ); }; - const onBlur = () => { + const onFocusOutside = () => { setIsExpanded( false ); }; @@ -151,7 +170,7 @@ function ComboboxControl( { ) : __( 'No results.' ); - speak( message, 'assertive' ); + speak( message, 'polite' ); } }, [ matchingSuggestions, isExpanded ] ); @@ -160,63 +179,74 @@ function ComboboxControl( { // TODO: Refactor click detection to use blur to stop propagation. /* eslint-disable jsx-a11y/no-static-element-interactions */ return ( - <BaseControl - className={ classnames( className, 'components-combobox-control' ) } - tabIndex="-1" - label={ label } - id={ `components-form-token-input-${ instanceId }` } - hideLabelFromVision={ hideLabelFromVision } - help={ help } - > - <div - className="components-combobox-control__suggestions-container" + <DetectOutside onFocusOutside={ onFocusOutside }> + <BaseControl + className={ classnames( + className, + 'components-combobox-control' + ) } tabIndex="-1" - onKeyDown={ onKeyDown } + label={ label } + id={ `components-form-token-input-${ instanceId }` } + hideLabelFromVision={ hideLabelFromVision } + help={ help } > - <Flex> - <FlexBlock> - <TokenInput - className="components-combobox-control__input" + <div + className="components-combobox-control__suggestions-container" + tabIndex="-1" + onKeyDown={ onKeyDown } + > + <Flex> + <FlexBlock> + <TokenInput + className="components-combobox-control__input" + instanceId={ instanceId } + ref={ inputContainer } + value={ isExpanded ? inputValue : currentLabel } + aria-label={ + currentLabel + ? `${ currentLabel }, ${ label }` + : null + } + onFocus={ onFocus } + isExpanded={ isExpanded } + selectedSuggestionIndex={ matchingSuggestions.indexOf( + selectedSuggestion + ) } + onChange={ onInputChange } + /> + </FlexBlock> + { allowReset && ( + <FlexItem> + <Button + className="components-combobox-control__reset" + icon={ closeSmall } + disabled={ ! value } + onClick={ handleOnReset } + label={ __( 'Reset' ) } + /> + </FlexItem> + ) } + </Flex> + { isExpanded && ( + <SuggestionsList instanceId={ instanceId } - ref={ inputContainer } - value={ isExpanded ? inputValue : currentLabel } - onBlur={ onBlur } - onFocus={ onFocus } - isExpanded={ isExpanded } - selectedSuggestionIndex={ matchingSuggestions.indexOf( + match={ { label: inputValue } } + displayTransform={ ( suggestion ) => + suggestion.label + } + suggestions={ matchingSuggestions } + selectedIndex={ matchingSuggestions.indexOf( selectedSuggestion ) } - onChange={ onInputChange } + onHover={ setSelectedSuggestion } + onSelect={ onSuggestionSelected } + scrollIntoView /> - </FlexBlock> - { allowReset && ( - <FlexItem> - <Button - className="components-combobox-control__reset" - icon={ closeSmall } - disabled={ ! value } - onClick={ handleOnReset } - label={ __( 'Reset' ) } - /> - </FlexItem> ) } - </Flex> - { isExpanded && ( - <SuggestionsList - instanceId={ instanceId } - match={ { label: inputValue } } - displayTransform={ ( suggestion ) => suggestion.label } - suggestions={ matchingSuggestions } - selectedIndex={ matchingSuggestions.indexOf( - selectedSuggestion - ) } - onHover={ setSelectedSuggestion } - onSelect={ onSuggestionSelected } - scrollIntoView - /> - ) } - </div> - </BaseControl> + </div> + </BaseControl> + </DetectOutside> ); /* eslint-enable jsx-a11y/no-static-element-interactions */ } diff --git a/packages/components/src/combobox-control/style.scss b/packages/components/src/combobox-control/style.scss index 1b5faf68b91cef..3cb0a8272e65c8 100644 --- a/packages/components/src/combobox-control/style.scss +++ b/packages/components/src/combobox-control/style.scss @@ -6,11 +6,18 @@ input.components-combobox-control__input[type="text"] { width: 100%; border: none; box-shadow: none; + font-size: 16px; padding: 2px; margin: 0; line-height: inherit; min-height: auto; + // Resolves Zooming on iOS devices + // https://github.com/WordPress/gutenberg/issues/27405 + @include break-small() { + font-size: 13px; + } + &:focus { outline: none; box-shadow: none; diff --git a/packages/components/src/custom-gradient-picker/index.js b/packages/components/src/custom-gradient-picker/index.js index 57220e5b40cf23..39e63ab404ed2d 100644 --- a/packages/components/src/custom-gradient-picker/index.js +++ b/packages/components/src/custom-gradient-picker/index.js @@ -13,7 +13,7 @@ import { __ } from '@wordpress/i18n'; */ import AnglePickerControl from '../angle-picker-control'; import CustomGradientBar from './custom-gradient-bar'; -import { Flex, FlexItem } from '../flex'; +import { Flex } from '../flex'; import SelectControl from '../select-control'; import { getGradientParsed } from './utils'; import { serializeGradient } from './serializer'; @@ -22,7 +22,10 @@ import { HORIZONTAL_GRADIENT_ORIENTATION, GRADIENT_OPTIONS, } from './constants'; -import { SelectWrapper } from './styles/custom-gradient-picker-styles'; +import { + AccessoryWrapper, + SelectWrapper, +} from './styles/custom-gradient-picker-styles'; const GradientAnglePicker = ( { gradientAST, hasGradient, onChange } ) => { const angle = get( @@ -43,8 +46,9 @@ const GradientAnglePicker = ( { gradientAST, hasGradient, onChange } ) => { }; return ( <AnglePickerControl - value={ hasGradient ? angle : '' } + hideLabelFromVision onChange={ onAngleChange } + value={ hasGradient ? angle : '' } /> ); }; @@ -85,6 +89,7 @@ const GradientTypePicker = ( { gradientAST, hasGradient, onChange } ) => { <SelectControl className="components-custom-gradient-picker__type-picker" label={ __( 'Type' ) } + labelPosition={ 'side' } onChange={ handleOnChange } options={ GRADIENT_OPTIONS } value={ hasGradient && type } @@ -109,15 +114,15 @@ export default function CustomGradientPicker( { value, onChange } ) { onChange={ onChange } /> </SelectWrapper> - { type === 'linear-gradient' && ( - <FlexItem> + <AccessoryWrapper> + { type === 'linear-gradient' && ( <GradientAnglePicker gradientAST={ gradientAST } hasGradient={ hasGradient } onChange={ onChange } /> - </FlexItem> - ) } + ) } + </AccessoryWrapper> </Flex> </div> ); diff --git a/packages/components/src/custom-gradient-picker/serializer.js b/packages/components/src/custom-gradient-picker/serializer.js index 90166311ce0b1d..c270ac50544cd7 100644 --- a/packages/components/src/custom-gradient-picker/serializer.js +++ b/packages/components/src/custom-gradient-picker/serializer.js @@ -13,7 +13,11 @@ export function serializeGradientColor( { type, value } ) { return `${ type }(${ value.join( ',' ) })`; } -export function serializeGradientPosition( { type, value } ) { +export function serializeGradientPosition( position ) { + if ( ! position ) { + return ''; + } + const { value, type } = position; return `${ value }${ type }`; } diff --git a/packages/components/src/custom-gradient-picker/styles/custom-gradient-picker-styles.js b/packages/components/src/custom-gradient-picker/styles/custom-gradient-picker-styles.js index f3d39295b163f3..f8c99b5a8800d6 100644 --- a/packages/components/src/custom-gradient-picker/styles/custom-gradient-picker-styles.js +++ b/packages/components/src/custom-gradient-picker/styles/custom-gradient-picker-styles.js @@ -5,8 +5,12 @@ import styled from '@emotion/styled'; /** * Internal dependencies */ -import { FlexItem } from '../../flex'; +import { FlexBlock } from '../../flex'; -export const SelectWrapper = styled( FlexItem )` - width: 110px; +export const SelectWrapper = styled( FlexBlock )` + flex-grow: 5; +`; + +export const AccessoryWrapper = styled( FlexBlock )` + flex-grow: 4; `; diff --git a/packages/components/src/custom-gradient-picker/utils.js b/packages/components/src/custom-gradient-picker/utils.js index 24fd56cec3bad2..a4212d2c3f1da4 100644 --- a/packages/components/src/custom-gradient-picker/utils.js +++ b/packages/components/src/custom-gradient-picker/utils.js @@ -256,6 +256,21 @@ const DIRECTIONAL_ORIENTATION_ANGLE_MAP = { 'left top': 315, }; +function hasUnsupportedLength( item ) { + return item.length === undefined || item.length.type !== '%'; +} + +function assignColorStopLengths( gradientAST ) { + const { colorStops } = gradientAST; + const step = 100 / ( colorStops.length - 1 ); + colorStops.forEach( ( stop, index ) => { + stop.length = { + value: step * index, + type: '%', + }; + } ); +} + export function getGradientParsed( value ) { let hasGradient = !! value; // gradientAST will contain the gradient AST as parsed by gradient-parser npm module. @@ -280,6 +295,12 @@ export function getGradientParsed( value ) { gradientAST.orientation.value ].toString(); } + + if ( gradientAST.colorStops.some( hasUnsupportedLength ) ) { + assignColorStopLengths( gradientAST ); + gradientValue = serializeGradient( gradientAST ); + } + return { hasGradient, gradientAST, diff --git a/packages/components/src/custom-select-control/index.js b/packages/components/src/custom-select-control/index.js index a8bbf9a93549ad..dbe1dcb38b1de7 100644 --- a/packages/components/src/custom-select-control/index.js +++ b/packages/components/src/custom-select-control/index.js @@ -148,13 +148,13 @@ export default function CustomSelectControl( { style: item.style, } ) } > + { item.name } { item === selectedItem && ( <Icon icon={ check } className="components-custom-select-control__item-icon" /> ) } - { item.name } </li> ) ) } </ul> diff --git a/packages/components/src/custom-select-control/style.scss b/packages/components/src/custom-select-control/style.scss index d3e8e8998e97aa..d71e8581d0e1d3 100644 --- a/packages/components/src/custom-select-control/style.scss +++ b/packages/components/src/custom-select-control/style.scss @@ -36,17 +36,18 @@ } .components-custom-select-control__menu { - background-color: $white; - - // Show border around the dropdown menu when open. - &:focus { - // Block UI appearance. - border: $border-width solid $gray-900; - border-radius: $radius-block-ui; - outline: none; - transition: none; + // Hide when collapsed. + &[aria-hidden="true"] { + display: none; } + // Block UI appearance. + border: $border-width solid $gray-900; + background-color: $white; + border-radius: $radius-block-ui; + outline: none; + transition: none; + max-height: 400px; min-width: 100%; overflow: auto; @@ -59,16 +60,21 @@ align-items: center; display: flex; list-style-type: none; - padding: 10px 5px 10px 25px; + padding: $grid-unit-10; cursor: default; + line-height: $icon-size + $grid-unit-05; &.is-highlighted { background: $gray-300; } - &-icon { - margin-left: -20px; + .components-custom-select-control__item-icon { margin-right: 0; + margin-left: auto; + } + + &:last-child { + margin-bottom: 0; } } diff --git a/packages/components/src/date-time/date.js b/packages/components/src/date-time/date.js index 4f395f4ebeff74..0b1ba7c19aa5fc 100644 --- a/packages/components/src/date-time/date.js +++ b/packages/components/src/date-time/date.js @@ -37,10 +37,14 @@ class DatePicker extends Component { if ( ! this.nodeRef.current ) { return; } + + const { ownerDocument } = this.nodeRef.current; + const { activeElement } = ownerDocument; + // If focus was lost. if ( - ! document.activeElement || - ! this.nodeRef.current.contains( document.activeElement ) + ! activeElement || + ! this.nodeRef.current.contains( ownerDocument.activeElement ) ) { // Retrieve the focus region div. const focusRegion = this.nodeRef.current.querySelector( diff --git a/packages/components/src/date-time/datepicker.scss b/packages/components/src/date-time/datepicker.scss new file mode 100644 index 00000000000000..2dfc20f396cc9a --- /dev/null +++ b/packages/components/src/date-time/datepicker.scss @@ -0,0 +1,861 @@ +/** + * Parts of this source were derived and modified from react-dates, + * released under the MIT license. + * + * https://github.com/airbnb/react-dates + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Airbnb + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// We don't convert styles to RTL because react-dates uses an isRTL flag instead. +/*rtl:begin:ignore*/ +.PresetDateRangePicker_panel { + padding: 0 22px 11px; +} +.PresetDateRangePicker_button { + position: relative; + height: 100%; + text-align: center; + background: 0 0; + border: 2px solid #00a699; + color: #00a699; + padding: 4px 12px; + margin-right: 8px; + font: inherit; + font-weight: 700; + line-height: normal; + overflow: visible; + -moz-box-sizing: border-box; + box-sizing: border-box; + cursor: pointer; +} +.PresetDateRangePicker_button:active { + outline: 0; +} +.PresetDateRangePicker_button__selected { + color: #fff; + background: #00a699; +} +.SingleDatePickerInput { + display: inline-block; + background-color: #fff; +} +.SingleDatePickerInput__withBorder { + border-radius: 2px; + border: 1px solid #dbdbdb; +} +.SingleDatePickerInput__rtl { + direction: rtl; +} +.SingleDatePickerInput__disabled { + background-color: #f2f2f2; +} +.SingleDatePickerInput__block { + display: block; +} +.SingleDatePickerInput__showClearDate { + padding-right: 30px; +} +.SingleDatePickerInput_clearDate { + background: 0 0; + border: 0; + color: inherit; + font: inherit; + line-height: normal; + overflow: visible; + cursor: pointer; + padding: 10px; + margin: 0 10px 0 5px; + position: absolute; + right: 0; + top: 50%; + -webkit-transform: translateY(-50%); + -ms-transform: translateY(-50%); + transform: translateY(-50%); +} +.SingleDatePickerInput_clearDate__default:focus, +.SingleDatePickerInput_clearDate__default:hover { + background: #dbdbdb; + border-radius: 50%; +} +.SingleDatePickerInput_clearDate__small { + padding: 6px; +} +.SingleDatePickerInput_clearDate__hide { + visibility: hidden; +} +.SingleDatePickerInput_clearDate_svg { + fill: #82888a; + height: 12px; + width: 15px; + vertical-align: middle; +} +.SingleDatePickerInput_clearDate_svg__small { + height: 9px; +} +.SingleDatePickerInput_calendarIcon { + background: 0 0; + border: 0; + color: inherit; + font: inherit; + line-height: normal; + overflow: visible; + cursor: pointer; + display: inline-block; + vertical-align: middle; + padding: 10px; + margin: 0 5px 0 10px; +} +.SingleDatePickerInput_calendarIcon_svg { + fill: #82888a; + height: 15px; + width: 14px; + vertical-align: middle; +} +.SingleDatePicker { + position: relative; + display: inline-block; +} +.SingleDatePicker__block { + display: block; +} +.SingleDatePicker_picker { + z-index: 1; + background-color: #fff; + position: absolute; +} +.SingleDatePicker_picker__rtl { + direction: rtl; +} +.SingleDatePicker_picker__directionLeft { + left: 0; +} +.SingleDatePicker_picker__directionRight { + right: 0; +} +.SingleDatePicker_picker__portal { + background-color: rgba(0, 0, 0, 0.3); + position: fixed; + top: 0; + left: 0; + height: 100%; + width: 100%; +} +.SingleDatePicker_picker__fullScreenPortal { + background-color: #fff; +} +.SingleDatePicker_closeButton { + background: 0 0; + border: 0; + color: inherit; + font: inherit; + line-height: normal; + overflow: visible; + cursor: pointer; + position: absolute; + top: 0; + right: 0; + padding: 15px; + z-index: 2; +} +.SingleDatePicker_closeButton:focus, +.SingleDatePicker_closeButton:hover { + color: darken(#cacccd, 10%); + text-decoration: none; +} +.SingleDatePicker_closeButton_svg { + height: 15px; + width: 15px; + fill: #cacccd; +} +.DayPickerKeyboardShortcuts_buttonReset { + background: 0 0; + border: 0; + border-radius: 0; + color: inherit; + font: inherit; + line-height: normal; + overflow: visible; + padding: 0; + cursor: pointer; + font-size: 14px; +} +.DayPickerKeyboardShortcuts_buttonReset:active { + outline: 0; +} +.DayPickerKeyboardShortcuts_show { + width: 22px; + position: absolute; + z-index: 2; +} +.DayPickerKeyboardShortcuts_show__bottomRight { + border-top: 26px solid transparent; + border-right: 33px solid #00a699; + bottom: 0; + right: 0; +} +.DayPickerKeyboardShortcuts_show__bottomRight:hover { + border-right: 33px solid #008489; +} +.DayPickerKeyboardShortcuts_show__topRight { + border-bottom: 26px solid transparent; + border-right: 33px solid #00a699; + top: 0; + right: 0; +} +.DayPickerKeyboardShortcuts_show__topRight:hover { + border-right: 33px solid #008489; +} +.DayPickerKeyboardShortcuts_show__topLeft { + border-bottom: 26px solid transparent; + border-left: 33px solid #00a699; + top: 0; + left: 0; +} +.DayPickerKeyboardShortcuts_show__topLeft:hover { + border-left: 33px solid #008489; +} +.DayPickerKeyboardShortcuts_showSpan { + color: #fff; + position: absolute; +} +.DayPickerKeyboardShortcuts_showSpan__bottomRight { + bottom: 0; + right: -28px; +} +.DayPickerKeyboardShortcuts_showSpan__topRight { + top: 1px; + right: -28px; +} +.DayPickerKeyboardShortcuts_showSpan__topLeft { + top: 1px; + left: -28px; +} +.DayPickerKeyboardShortcuts_panel { + overflow: auto; + background: #fff; + border: 1px solid #dbdbdb; + border-radius: 2px; + position: absolute; + top: 0; + bottom: 0; + right: 0; + left: 0; + z-index: 2; + padding: 22px; + margin: 33px; +} +.DayPickerKeyboardShortcuts_title { + font-size: 16px; + font-weight: 700; + margin: 0; +} +.DayPickerKeyboardShortcuts_list { + list-style: none; + padding: 0; + font-size: 14px; +} +.DayPickerKeyboardShortcuts_close { + position: absolute; + right: 22px; + top: 22px; + z-index: 2; +} +.DayPickerKeyboardShortcuts_close:active { + outline: 0; +} +.DayPickerKeyboardShortcuts_closeSvg { + height: 15px; + width: 15px; + fill: #cacccd; +} +.DayPickerKeyboardShortcuts_closeSvg:focus, +.DayPickerKeyboardShortcuts_closeSvg:hover { + fill: #82888a; +} +.CalendarDay { + -moz-box-sizing: border-box; + box-sizing: border-box; + cursor: pointer; + font-size: 14px; + text-align: center; +} +.CalendarDay:active { + outline: 0; +} +.CalendarDay__defaultCursor { + cursor: default; +} +.CalendarDay__default { + border: 1px solid #e4e7e7; + color: #484848; + background: #fff; +} +.CalendarDay__default:hover { + background: #e4e7e7; + border: 1px double #e4e7e7; + color: inherit; +} +.CalendarDay__hovered_offset { + background: #f4f5f5; + border: 1px double #e4e7e7; + color: inherit; +} +.CalendarDay__outside { + border: 0; + background: #fff; + color: #484848; +} +.CalendarDay__outside:hover { + border: 0; +} +.CalendarDay__blocked_minimum_nights { + background: #fff; + border: 1px solid #eceeee; + color: #cacccd; +} +.CalendarDay__blocked_minimum_nights:active, +.CalendarDay__blocked_minimum_nights:hover { + background: #fff; + color: #cacccd; +} +.CalendarDay__highlighted_calendar { + background: #ffe8bc; + color: #484848; +} +.CalendarDay__highlighted_calendar:active, +.CalendarDay__highlighted_calendar:hover { + background: #ffce71; + color: #484848; +} +.CalendarDay__selected_span { + background: #66e2da; + border: 1px solid #33dacd; + color: #fff; +} +.CalendarDay__selected_span:active, +.CalendarDay__selected_span:hover { + background: #33dacd; + border: 1px solid #33dacd; + color: #fff; +} +.CalendarDay__last_in_range { + border-right: #00a699; +} +.CalendarDay__selected, +.CalendarDay__selected:active, +.CalendarDay__selected:hover { + background: #00a699; + border: 1px solid #00a699; + color: #fff; +} +.CalendarDay__hovered_span, +.CalendarDay__hovered_span:hover { + background: #b2f1ec; + border: 1px solid #80e8e0; + color: #007a87; +} +.CalendarDay__hovered_span:active { + background: #80e8e0; + border: 1px solid #80e8e0; + color: #007a87; +} +.CalendarDay__blocked_calendar, +.CalendarDay__blocked_calendar:active, +.CalendarDay__blocked_calendar:hover { + background: #cacccd; + border: 1px solid #cacccd; + color: #82888a; +} +.CalendarDay__blocked_out_of_range, +.CalendarDay__blocked_out_of_range:active, +.CalendarDay__blocked_out_of_range:hover { + background: #fff; + border: 1px solid #e4e7e7; + color: #cacccd; +} +.CalendarMonth { + background: #fff; + text-align: center; + vertical-align: top; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.CalendarMonth_table { + border-collapse: collapse; + border-spacing: 0; +} +.CalendarMonth_verticalSpacing { + border-collapse: separate; +} +.CalendarMonth_caption { + color: #484848; + font-size: 18px; + text-align: center; + padding-top: 22px; + padding-bottom: 37px; + caption-side: initial; +} +.CalendarMonth_caption__verticalScrollable { + padding-top: 12px; + padding-bottom: 7px; +} +.CalendarMonthGrid { + background: #fff; + text-align: left; + z-index: 0; +} +.CalendarMonthGrid__animating { + z-index: 1; +} +.CalendarMonthGrid__horizontal { + position: absolute; + left: 9px; +} +.CalendarMonthGrid__vertical { + margin: 0 auto; +} +.CalendarMonthGrid__vertical_scrollable { + margin: 0 auto; + overflow-y: scroll; +} +.CalendarMonthGrid_month__horizontal { + display: inline-block; + vertical-align: top; + min-height: 100%; +} +.CalendarMonthGrid_month__hideForAnimation { + position: absolute; + z-index: -1; + opacity: 0; + pointer-events: none; +} +.CalendarMonthGrid_month__hidden { + visibility: hidden; +} +.DayPickerNavigation { + position: relative; + z-index: 2; +} +.DayPickerNavigation__horizontal { + height: 0; +} +.DayPickerNavigation__verticalDefault { + position: absolute; + width: 100%; + height: 52px; + bottom: 0; + left: 0; +} +.DayPickerNavigation__verticalScrollableDefault { + position: relative; +} +.DayPickerNavigation_button { + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + border: 0; + padding: 0; + margin: 0; +} +.DayPickerNavigation_button__default { + border: 1px solid #e4e7e7; + background-color: #fff; + color: #757575; +} +.DayPickerNavigation_button__default:focus, +.DayPickerNavigation_button__default:hover { + border: 1px solid #c4c4c4; +} +.DayPickerNavigation_button__default:active { + background: #f2f2f2; +} +.DayPickerNavigation_button__horizontalDefault { + position: absolute; + top: 18px; + line-height: 0.78; + border-radius: 3px; + padding: 6px 9px; +} +.DayPickerNavigation_leftButton__horizontalDefault { + left: 22px; +} +.DayPickerNavigation_rightButton__horizontalDefault { + right: 22px; +} +.DayPickerNavigation_button__verticalDefault { + padding: 5px; + background: #fff; + box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.1); + position: relative; + display: inline-block; + height: 100%; + width: 50%; +} +.DayPickerNavigation_nextButton__verticalDefault { + border-left: 0; +} +.DayPickerNavigation_nextButton__verticalScrollableDefault { + width: 100%; +} +.DayPickerNavigation_svg__horizontal { + height: 19px; + width: 19px; + fill: #82888a; + display: block; +} +.DayPickerNavigation_svg__vertical { + height: 42px; + width: 42px; + fill: #484848; + display: block; +} +.DayPicker { + background: #fff; + position: relative; + text-align: left; +} +.DayPicker__horizontal { + background: #fff; +} +.DayPicker__verticalScrollable { + height: 100%; +} +.DayPicker__hidden { + visibility: hidden; +} +.DayPicker__withBorder { + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(0, 0, 0, 0.07); + border-radius: 3px; +} +.DayPicker_portal__horizontal { + box-shadow: none; + position: absolute; + left: 50%; + top: 50%; +} +.DayPicker_portal__vertical { + position: initial; +} +.DayPicker_focusRegion { + outline: 0; +} +.DayPicker_calendarInfo__horizontal, +.DayPicker_wrapper__horizontal { + display: inline-block; + vertical-align: top; +} +.DayPicker_weekHeaders { + position: relative; +} +.DayPicker_weekHeaders__horizontal { + margin-left: 9px; +} +.DayPicker_weekHeader { + color: #757575; + position: absolute; + top: 62px; + z-index: 2; + text-align: left; +} +.DayPicker_weekHeader__vertical { + left: 50%; +} +.DayPicker_weekHeader__verticalScrollable { + top: 0; + display: table-row; + border-bottom: 1px solid #dbdbdb; + background: #fff; + margin-left: 0; + left: 0; + width: 100%; + text-align: center; +} +.DayPicker_weekHeader_ul { + list-style: none; + margin: 1px 0; + padding-left: 0; + padding-right: 0; + font-size: 14px; +} +.DayPicker_weekHeader_li { + display: inline-block; + text-align: center; +} +.DayPicker_transitionContainer { + position: relative; + overflow: hidden; + border-radius: 3px; +} +.DayPicker_transitionContainer__horizontal { + -webkit-transition: height 0.2s ease-in-out; + -moz-transition: height 0.2s ease-in-out; + transition: height 0.2s ease-in-out; +} +.DayPicker_transitionContainer__vertical { + width: 100%; +} +.DayPicker_transitionContainer__verticalScrollable { + padding-top: 20px; + height: 100%; + position: absolute; + top: 0; + bottom: 0; + right: 0; + left: 0; + overflow-y: scroll; +} +.DateInput { + margin: 0; + padding: 0; + background: #fff; + position: relative; + display: inline-block; + width: 130px; + vertical-align: middle; +} +.DateInput__small { + width: 97px; +} +.DateInput__block { + width: 100%; +} +.DateInput__disabled { + background: #f2f2f2; + color: #dbdbdb; +} +.DateInput_input { + font-weight: 200; + font-size: 19px; + line-height: 24px; + color: #484848; + background-color: #fff; + width: 100%; + padding: 11px 11px 9px; + border: 0; + border-top: 0; + border-right: 0; + border-bottom: 2px solid transparent; + border-left: 0; + border-radius: 0; +} +.DateInput_input__small { + font-size: 15px; + line-height: 18px; + letter-spacing: 0.2px; + padding: 7px 7px 5px; +} +.DateInput_input__regular { + font-weight: auto; +} +.DateInput_input__readOnly { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.DateInput_input__focused { + outline: 0; + background: #fff; + border: 0; + border-top: 0; + border-right: 0; + border-bottom: 2px solid #008489; + border-left: 0; +} +.DateInput_input__disabled { + background: #f2f2f2; + font-style: italic; +} +.DateInput_screenReaderMessage { + border: 0; + clip: rect(0, 0, 0, 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} +.DateInput_fang { + position: absolute; + width: 20px; + height: 10px; + left: 22px; + z-index: 2; +} +.DateInput_fangShape { + fill: #fff; +} +.DateInput_fangStroke { + stroke: #dbdbdb; + fill: transparent; +} +.DateRangePickerInput { + background-color: #fff; + display: inline-block; +} +.DateRangePickerInput__disabled { + background: #f2f2f2; +} +.DateRangePickerInput__withBorder { + border-radius: 2px; + border: 1px solid #dbdbdb; +} +.DateRangePickerInput__rtl { + direction: rtl; +} +.DateRangePickerInput__block { + display: block; +} +.DateRangePickerInput__showClearDates { + padding-right: 30px; +} +.DateRangePickerInput_arrow { + display: inline-block; + vertical-align: middle; + color: #484848; +} +.DateRangePickerInput_arrow_svg { + vertical-align: middle; + fill: #484848; + height: 24px; + width: 24px; +} +.DateRangePickerInput_clearDates { + background: 0 0; + border: 0; + color: inherit; + font: inherit; + line-height: normal; + overflow: visible; + cursor: pointer; + padding: 10px; + margin: 0 10px 0 5px; + position: absolute; + right: 0; + top: 50%; + -webkit-transform: translateY(-50%); + -ms-transform: translateY(-50%); + transform: translateY(-50%); +} +.DateRangePickerInput_clearDates__small { + padding: 6px; +} +.DateRangePickerInput_clearDates_default:focus, +.DateRangePickerInput_clearDates_default:hover { + background: #dbdbdb; + border-radius: 50%; +} +.DateRangePickerInput_clearDates__hide { + visibility: hidden; +} +.DateRangePickerInput_clearDates_svg { + fill: #82888a; + height: 12px; + width: 15px; + vertical-align: middle; +} +.DateRangePickerInput_clearDates_svg__small { + height: 9px; +} +.DateRangePickerInput_calendarIcon { + background: 0 0; + border: 0; + color: inherit; + font: inherit; + line-height: normal; + overflow: visible; + cursor: pointer; + display: inline-block; + vertical-align: middle; + padding: 10px; + margin: 0 5px 0 10px; +} +.DateRangePickerInput_calendarIcon_svg { + fill: #82888a; + height: 15px; + width: 14px; + vertical-align: middle; +} +.DateRangePicker { + position: relative; + display: inline-block; +} +.DateRangePicker__block { + display: block; +} +.DateRangePicker_picker { + z-index: 1; + background-color: #fff; + position: absolute; +} +.DateRangePicker_picker__rtl { + direction: rtl; +} +.DateRangePicker_picker__directionLeft { + left: 0; +} +.DateRangePicker_picker__directionRight { + right: 0; +} +.DateRangePicker_picker__portal { + background-color: rgba(0, 0, 0, 0.3); + position: fixed; + top: 0; + left: 0; + height: 100%; + width: 100%; +} +.DateRangePicker_picker__fullScreenPortal { + background-color: #fff; +} +.DateRangePicker_closeButton { + background: 0 0; + border: 0; + color: inherit; + font: inherit; + line-height: normal; + overflow: visible; + cursor: pointer; + position: absolute; + top: 0; + right: 0; + padding: 15px; + z-index: 2; +} +.DateRangePicker_closeButton:focus, +.DateRangePicker_closeButton:hover { + color: darken(#cacccd, 10%); + text-decoration: none; +} +.DateRangePicker_closeButton_svg { + height: 15px; + width: 15px; + fill: #cacccd; +} +/*rtl:end:ignore*/ diff --git a/packages/components/src/date-time/index.js b/packages/components/src/date-time/index.js index 32d5c5535ed273..fecc9e302f29e1 100644 --- a/packages/components/src/date-time/index.js +++ b/packages/components/src/date-time/index.js @@ -8,7 +8,7 @@ import 'react-dates/initialize'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { useState, forwardRef } from '@wordpress/element'; import { __, _x } from '@wordpress/i18n'; /** @@ -20,169 +20,149 @@ import { default as TimePicker } from './time'; export { DatePicker, TimePicker }; -export class DateTimePicker extends Component { - constructor() { - super( ...arguments ); +function DateTimePicker( + { + currentDate, + is12Hour, + isInvalidDate, + onMonthPreviewed, + onChange, + events, + }, + ref +) { + const [ calendarHelpIsVisible, setCalendarHelpIsVisible ] = useState( + false + ); - this.state = { calendarHelpIsVisible: false }; - - this.onClickDescriptionToggle = this.onClickDescriptionToggle.bind( - this - ); - } - - onClickDescriptionToggle() { - this.setState( { - calendarHelpIsVisible: ! this.state.calendarHelpIsVisible, - } ); + function onClickDescriptionToggle() { + setCalendarHelpIsVisible( ! calendarHelpIsVisible ); } - render() { - const { - currentDate, - is12Hour, - isInvalidDate, - onMonthPreviewed, - onChange, - events, - } = this.props; - - return ( - <div className="components-datetime"> - { ! this.state.calendarHelpIsVisible && ( - <> - <TimePicker - currentTime={ currentDate } - onChange={ onChange } - is12Hour={ is12Hour } - /> - <DatePicker - currentDate={ currentDate } - onChange={ onChange } - isInvalidDate={ isInvalidDate } - onMonthPreviewed={ onMonthPreviewed } - events={ events } - /> - </> - ) } - - { this.state.calendarHelpIsVisible && ( - <> - <div className="components-datetime__calendar-help"> - <h4>{ __( 'Click to Select' ) }</h4> - <ul> - <li> - { __( - 'Click the right or left arrows to select other months in the past or the future.' - ) } - </li> - <li> - { __( - 'Click the desired day to select it.' - ) } - </li> - </ul> - - <h4>{ __( 'Navigating with a keyboard' ) }</h4> - <ul> - <li> - <abbr - aria-label={ _x( - 'Enter', - 'keyboard button' - ) } - > - ↵ - </abbr> - { - ' ' /* JSX removes whitespace, but a space is required for screen readers. */ - } - <span> - { __( 'Select the date in focus.' ) } - </span> - </li> - <li> - <abbr - aria-label={ __( - 'Left and Right Arrows' - ) } - > - ←/→ - </abbr> - { - ' ' /* JSX removes whitespace, but a space is required for screen readers. */ - } - { __( - 'Move backward (left) or forward (right) by one day.' + return ( + <div ref={ ref } className="components-datetime"> + { ! calendarHelpIsVisible && ( + <> + <TimePicker + currentTime={ currentDate } + onChange={ onChange } + is12Hour={ is12Hour } + /> + <DatePicker + currentDate={ currentDate } + onChange={ onChange } + isInvalidDate={ isInvalidDate } + onMonthPreviewed={ onMonthPreviewed } + events={ events } + /> + </> + ) } + { calendarHelpIsVisible && ( + <> + <div className="components-datetime__calendar-help"> + <h4>{ __( 'Click to Select' ) }</h4> + <ul> + <li> + { __( + 'Click the right or left arrows to select other months in the past or the future.' + ) } + </li> + <li> + { __( 'Click the desired day to select it.' ) } + </li> + </ul> + <h4>{ __( 'Navigating with a keyboard' ) }</h4> + <ul> + <li> + <abbr + aria-label={ _x( + 'Enter', + 'keyboard button' ) } - </li> - <li> - <abbr - aria-label={ __( - 'Up and Down Arrows' - ) } - > - ↑/↓ - </abbr> - { - ' ' /* JSX removes whitespace, but a space is required for screen readers. */ - } - { __( - 'Move backward (up) or forward (down) by one week.' - ) } - </li> - <li> - <abbr - aria-label={ __( - 'Page Up and Page Down' - ) } - > - { __( 'PgUp/PgDn' ) } - </abbr> - { - ' ' /* JSX removes whitespace, but a space is required for screen readers. */ - } - { __( - 'Move backward (PgUp) or forward (PgDn) by one month.' - ) } - </li> - <li> - <abbr aria-label={ __( 'Home and End' ) }> - { __( 'Home/End' ) } - </abbr> - { - ' ' /* JSX removes whitespace, but a space is required for screen readers. */ - } - { __( - 'Go to the first (home) or last (end) day of a week.' - ) } - </li> - </ul> - </div> - </> - ) } - - <div className="components-datetime__buttons"> - { ! this.state.calendarHelpIsVisible && currentDate && ( - <Button - className="components-datetime__date-reset-button" - isLink - onClick={ () => onChange( null ) } - > - { __( 'Reset' ) } - </Button> - ) } + > + ↵ + </abbr> + { + ' ' /* JSX removes whitespace, but a space is required for screen readers. */ + } + <span> + { __( 'Select the date in focus.' ) } + </span> + </li> + <li> + <abbr + aria-label={ __( 'Left and Right Arrows' ) } + > + ←/→ + </abbr> + { + ' ' /* JSX removes whitespace, but a space is required for screen readers. */ + } + { __( + 'Move backward (left) or forward (right) by one day.' + ) } + </li> + <li> + <abbr aria-label={ __( 'Up and Down Arrows' ) }> + ↑/↓ + </abbr> + { + ' ' /* JSX removes whitespace, but a space is required for screen readers. */ + } + { __( + 'Move backward (up) or forward (down) by one week.' + ) } + </li> + <li> + <abbr + aria-label={ __( 'Page Up and Page Down' ) } + > + { __( 'PgUp/PgDn' ) } + </abbr> + { + ' ' /* JSX removes whitespace, but a space is required for screen readers. */ + } + { __( + 'Move backward (PgUp) or forward (PgDn) by one month.' + ) } + </li> + <li> + <abbr aria-label={ __( 'Home and End' ) }> + { __( 'Home/End' ) } + </abbr> + { + ' ' /* JSX removes whitespace, but a space is required for screen readers. */ + } + { __( + 'Go to the first (home) or last (end) day of a week.' + ) } + </li> + </ul> + </div> + </> + ) } + <div className="components-datetime__buttons"> + { ! calendarHelpIsVisible && currentDate && ( <Button - className="components-datetime__date-help-toggle" + className="components-datetime__date-reset-button" isLink - onClick={ this.onClickDescriptionToggle } + onClick={ () => onChange( null ) } > - { this.state.calendarHelpIsVisible - ? __( 'Close' ) - : __( 'Calendar Help' ) } + { __( 'Reset' ) } </Button> - </div> + ) } + <Button + className="components-datetime__date-help-toggle" + isLink + onClick={ onClickDescriptionToggle } + > + { calendarHelpIsVisible + ? __( 'Close' ) + : __( 'Calendar Help' ) } + </Button> </div> - ); - } + </div> + ); } + +export default forwardRef( DateTimePicker ); diff --git a/packages/components/src/date-time/style.scss b/packages/components/src/date-time/style.scss index fca0c397314998..85bec7b5f4872d 100644 --- a/packages/components/src/date-time/style.scss +++ b/packages/components/src/date-time/style.scss @@ -1,8 +1,4 @@ -// We can't reference this package with ~ because of how Lerna handles packages. -// Also, don't convert styles to RTL because react-dates uses an isRTL flag instead. -/*rtl:begin:ignore*/ -@import "node_modules/react-dates/lib/css/_datepicker"; -/*rtl:end:ignore*/ +@import "./datepicker"; .components-datetime { padding: 0; diff --git a/packages/components/src/date-time/test/time.js b/packages/components/src/date-time/test/time.js index 32a47ca9ed2dc6..7124e80e6c1b1a 100644 --- a/packages/components/src/date-time/test/time.js +++ b/packages/components/src/date-time/test/time.js @@ -271,4 +271,28 @@ describe( 'TimePicker', () => { expect( monthInputIndex > dayInputIndex ).toBe( true ); } ); + + it( 'Should set a time when passed a null currentTime', () => { + const onChangeSpy = jest.fn(); + + render( + <TimePicker + currentTime={ null } + onChange={ onChangeSpy } + is12Hour + /> + ); + + const monthInput = screen.getByLabelText( 'Month' ).value; + const dayInput = screen.getByLabelText( 'Day' ).value; + const yearInput = screen.getByLabelText( 'Year' ).value; + const hoursInput = screen.getByLabelText( 'Hours' ).value; + const minutesInput = screen.getByLabelText( 'Minutes' ).value; + + expect( Number.isNaN( parseInt( monthInput, 10 ) ) ).toBe( false ); + expect( Number.isNaN( parseInt( dayInput, 10 ) ) ).toBe( false ); + expect( Number.isNaN( parseInt( yearInput, 10 ) ) ).toBe( false ); + expect( Number.isNaN( parseInt( hoursInput, 10 ) ) ).toBe( false ); + expect( Number.isNaN( parseInt( minutesInput, 10 ) ) ).toBe( false ); + } ); } ); diff --git a/packages/components/src/date-time/time.js b/packages/components/src/date-time/time.js index 5df865e95e27d4..de10573f992524 100644 --- a/packages/components/src/date-time/time.js +++ b/packages/components/src/date-time/time.js @@ -88,7 +88,9 @@ export function TimePicker( { is12Hour, currentTime, onChange } ) { // Reset the state when currentTime changed. useEffect( () => { - setDate( moment( currentTime ).startOf( 'minutes' ) ); + setDate( + currentTime ? moment( currentTime ).startOf( 'minutes' ) : moment() + ); }, [ currentTime ] ); const { day, month, year, minutes, hours, am } = useMemo( diff --git a/packages/components/src/disabled/README.md b/packages/components/src/disabled/README.md index 3b823b120be1a1..cb871d5d837cf6 100644 --- a/packages/components/src/disabled/README.md +++ b/packages/components/src/disabled/README.md @@ -49,3 +49,15 @@ function CustomButton() { ); } ``` + +### Props + +The component accepts the following props: + +#### isDisabled + +Whether to disable all the descendant fields. Defaults to `true`. + +- Type: `Boolean` +- Required: No +- Default: `true` diff --git a/packages/components/src/disabled/index.js b/packages/components/src/disabled/index.js index e46f876ac2765a..2e7cab577a2bd1 100644 --- a/packages/components/src/disabled/index.js +++ b/packages/components/src/disabled/index.js @@ -15,6 +15,11 @@ import { } from '@wordpress/element'; import { focus } from '@wordpress/dom'; +/** + * Internal dependencies + */ +import { StyledWrapper } from './styles/disabled-styles'; + const { Consumer, Provider } = createContext( false ); /** @@ -36,7 +41,7 @@ const DISABLED_ELIGIBLE_NODE_NAMES = [ 'TEXTAREA', ]; -function Disabled( { className, children, ...props } ) { +function Disabled( { className, children, isDisabled = true, ...props } ) { const node = useRef(); const disable = () => { @@ -70,6 +75,10 @@ function Disabled( { className, children, ...props } ) { ); useLayoutEffect( () => { + if ( ! isDisabled ) { + return; + } + disable(); const observer = new window.MutationObserver( debouncedDisable ); @@ -85,15 +94,19 @@ function Disabled( { className, children, ...props } ) { }; }, [] ); + if ( ! isDisabled ) { + return <Provider value={ false }>{ children }</Provider>; + } + return ( <Provider value={ true }> - <div + <StyledWrapper ref={ node } className={ classnames( className, 'components-disabled' ) } { ...props } > { children } - </div> + </StyledWrapper> </Provider> ); } diff --git a/packages/components/src/disabled/stories/index.js b/packages/components/src/disabled/stories/index.js index 527c76899c6527..fb994153c1037c 100644 --- a/packages/components/src/disabled/stories/index.js +++ b/packages/components/src/disabled/stories/index.js @@ -1,7 +1,13 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + /** * Internal dependencies */ import Disabled from '../'; +import Button from '../../button/'; import SelectControl from '../../select-control/'; import TextControl from '../../text-control/'; import TextareaControl from '../../textarea-control/'; @@ -11,21 +17,45 @@ export default { component: Disabled, }; +const Form = () => ( + <div> + <TextControl label="Text Control" /> + <TextareaControl label="TextArea Control" /> + <SelectControl + label="Select Control" + onChange={ () => {} } + options={ [ + { value: null, label: 'Select an option', disabled: true }, + { value: 'a', label: 'Option A' }, + { value: 'b', label: 'Option B' }, + { value: 'c', label: 'Option C' }, + ] } + /> + </div> +); + export const _default = () => { return ( <Disabled> - <TextControl label="Text Control" /> - <TextareaControl label="TextArea Control" /> - <SelectControl - label="Select Control" - onChange={ () => {} } - options={ [ - { value: null, label: 'Select an option', disabled: true }, - { value: 'a', label: 'Option A' }, - { value: 'b', label: 'Option B' }, - { value: 'c', label: 'Option C' }, - ] } - /> + <Form /> </Disabled> ); }; + +export const DisabledWithProp = () => { + const [ isDisabled, setState ] = useState( true ); + const toggleDisabled = () => { + setState( () => ! isDisabled ); + }; + + return ( + <div> + <Disabled isDisabled={ isDisabled }> + <Form /> + </Disabled> + <Button isPrimary onClick={ toggleDisabled }> + Set isDisabled to { isDisabled ? 'false' : 'true' } + </Button> + </div> + ); +}; diff --git a/packages/components/src/disabled/style.scss b/packages/components/src/disabled/styles/disabled-styles.js similarity index 60% rename from packages/components/src/disabled/style.scss rename to packages/components/src/disabled/styles/disabled-styles.js index 77e0dab63a0b88..b0ebeef8873972 100644 --- a/packages/components/src/disabled/style.scss +++ b/packages/components/src/disabled/styles/disabled-styles.js @@ -1,9 +1,14 @@ -.components-disabled { +/** + * External dependencies + */ +import styled from '@emotion/styled'; + +export const StyledWrapper = styled.div` position: relative; pointer-events: none; &::after { - content: ""; + content: ''; position: absolute; top: 0; right: 0; @@ -15,4 +20,4 @@ * { pointer-events: none; } -} +`; diff --git a/packages/components/src/disabled/test/index.js b/packages/components/src/disabled/test/index.js index bef7a9e9e1e6cd..57697d51ba13fa 100644 --- a/packages/components/src/disabled/test/index.js +++ b/packages/components/src/disabled/test/index.js @@ -68,9 +68,9 @@ describe( 'Disabled', () => { // this is needed because TestUtils does not accept a stateless component. class DisabledComponent extends Component { render() { - const { children } = this.props; + const { children, isDisabled } = this.props; - return <Disabled>{ children }</Disabled>; + return <Disabled isDisabled={ isDisabled }>{ children }</Disabled>; } } @@ -133,6 +133,51 @@ describe( 'Disabled', () => { expect( div.hasAttribute( 'tabindex' ) ).toBe( true ); } ); + it( 'will disable or enable descendant fields based on the isDisabled prop value', () => { + class MaybeDisable extends Component { + constructor() { + super( ...arguments ); + this.state = { isDisabled: true }; + } + + render() { + return ( + <DisabledComponent isDisabled={ this.state.isDisabled }> + <Form /> + </DisabledComponent> + ); + } + } + + const wrapper = TestUtils.renderIntoDocument( <MaybeDisable /> ); + + const input = TestUtils.findRenderedDOMComponentWithTag( + wrapper, + 'input' + ); + const div = TestUtils.scryRenderedDOMComponentsWithTag( + wrapper, + 'div' + )[ 1 ]; + + expect( input.hasAttribute( 'disabled' ) ).toBe( true ); + expect( div.getAttribute( 'contenteditable' ) ).toBe( 'false' ); + + wrapper.setState( { isDisabled: false } ); + + const input2 = TestUtils.findRenderedDOMComponentWithTag( + wrapper, + 'input' + ); + const div2 = TestUtils.scryRenderedDOMComponentsWithTag( + wrapper, + 'div' + )[ 0 ]; + + expect( input2.hasAttribute( 'disabled' ) ).toBe( false ); + expect( div2.getAttribute( 'contenteditable' ) ).not.toBe( 'false' ); + } ); + // Ideally, we'd have two more test cases here: // // - it( 'will disable all fields on component render change' ) @@ -170,6 +215,19 @@ describe( 'Disabled', () => { expect( wrapperElement.textContent ).toBe( 'Disabled' ); } ); + test( "lets components know that they're not disabled via context when isDisabled is false", () => { + const wrapper = TestUtils.renderIntoDocument( + <DisabledComponent isDisabled={ false }> + <DisabledStatus /> + </DisabledComponent> + ); + const wrapperElement = TestUtils.findRenderedDOMComponentWithTag( + wrapper, + 'p' + ); + expect( wrapperElement.textContent ).toBe( 'Not disabled' ); + } ); + test( "lets components know that they're not disabled via context", () => { const wrapper = TestUtils.renderIntoDocument( <DisabledStatus /> ); const wrapperElement = TestUtils.findRenderedDOMComponentWithTag( diff --git a/packages/components/src/draggable/index.js b/packages/components/src/draggable/index.js index 251eeeb8848c6e..cefb6c7cec1598 100644 --- a/packages/components/src/draggable/index.js +++ b/packages/components/src/draggable/index.js @@ -1,77 +1,39 @@ -/** - * External dependencies - */ -import { noop } from 'lodash'; - /** * WordPress dependencies */ -import { Component, createRef } from '@wordpress/element'; -import { withSafeTimeout } from '@wordpress/compose'; +import { useEffect, useRef } from '@wordpress/element'; const dragImageClass = 'components-draggable__invisible-drag-image'; const cloneWrapperClass = 'components-draggable__clone'; const cloneHeightTransformationBreakpoint = 700; const clonePadding = 0; - -class Draggable extends Component { - constructor() { - super( ...arguments ); - - this.onDragStart = this.onDragStart.bind( this ); - this.onDragOver = this.onDragOver.bind( this ); - this.onDragEnd = this.onDragEnd.bind( this ); - this.resetDragState = this.resetDragState.bind( this ); - this.dragComponentRef = createRef(); - } - - componentWillUnmount() { - this.resetDragState(); - } +const bodyClass = 'is-dragging-components-draggable'; + +export default function Draggable( { + children, + onDragStart, + onDragOver, + onDragEnd, + cloneClassname, + elementId, + transferData, + __experimentalDragComponent: dragComponent, +} ) { + const dragComponentRef = useRef(); + const cleanup = useRef( () => {} ); /** * Removes the element clone, resets cursor, and removes drag listener. * - * @param {Object} event The non-custom DragEvent. + * @param {Object} event The non-custom DragEvent. */ - onDragEnd( event ) { - const { onDragEnd = noop } = this.props; + function end( event ) { event.preventDefault(); + cleanup.current(); - this.resetDragState(); - - // Allow the Synthetic Event to be accessed from asynchronous code. - // https://reactjs.org/docs/events.html#event-pooling - event.persist(); - this.props.setTimeout( onDragEnd.bind( this, event ) ); - } - - /** - * Updates positioning of element clone based on mouse movement during dragging. - * - * @param {Object} event The non-custom DragEvent. - */ - onDragOver( event ) { - this.cloneWrapper.style.top = `${ - parseInt( this.cloneWrapper.style.top, 10 ) + - event.clientY - - this.cursorTop - }px`; - this.cloneWrapper.style.left = `${ - parseInt( this.cloneWrapper.style.left, 10 ) + - event.clientX - - this.cursorLeft - }px`; - - // Update cursor coordinates. - this.cursorLeft = event.clientX; - this.cursorTop = event.clientY; - - const { onDragOver = noop } = this.props; - - // The `event` from `onDragOver` is not a SyntheticEvent - // and so it doesn't require `event.persist()`. - this.props.setTimeout( onDragOver.bind( this, event ) ); + if ( onDragOver ) { + onDragEnd( event ); + } } /** @@ -82,79 +44,73 @@ class Draggable extends Component { * - Sets transfer data. * - Adds dragover listener. * - * @param {Object} event The non-custom DragEvent. + * @param {Object} event The non-custom DragEvent. */ - onDragStart( event ) { - const { - cloneClassname, - elementId, - transferData, - onDragStart = noop, - } = this.props; - const element = document.getElementById( elementId ); - if ( ! element ) { - event.preventDefault(); - return; - } + function start( event ) { + const { ownerDocument } = event.target; + + event.dataTransfer.setData( 'text', JSON.stringify( transferData ) ); + + const cloneWrapper = ownerDocument.createElement( 'div' ); + const dragImage = ownerDocument.createElement( 'div' ); // Set a fake drag image to avoid browser defaults. Remove from DOM // right after. event.dataTransfer.setDragImage is not supported yet in // IE, we need to check for its existence first. if ( 'function' === typeof event.dataTransfer.setDragImage ) { - const dragImage = document.createElement( 'div' ); - dragImage.id = `drag-image-${ elementId }`; dragImage.classList.add( dragImageClass ); - document.body.appendChild( dragImage ); + ownerDocument.body.appendChild( dragImage ); event.dataTransfer.setDragImage( dragImage, 0, 0 ); - this.props.setTimeout( () => { - document.body.removeChild( dragImage ); - } ); } - event.dataTransfer.setData( 'text', JSON.stringify( transferData ) ); + cloneWrapper.classList.add( cloneWrapperClass ); - // Prepare element clone and append to element wrapper. - const elementRect = element.getBoundingClientRect(); - const elementWrapper = element.parentNode; - const elementTopOffset = parseInt( elementRect.top, 10 ); - const elementLeftOffset = parseInt( elementRect.left, 10 ); - this.cloneWrapper = document.createElement( 'div' ); - this.cloneWrapper.classList.add( cloneWrapperClass ); if ( cloneClassname ) { - this.cloneWrapper.classList.add( cloneClassname ); + cloneWrapper.classList.add( cloneClassname ); } - this.cloneWrapper.style.width = `${ - elementRect.width + clonePadding * 2 - }px`; - // If a dragComponent is defined, the following logic will clone the // HTML node and inject it into the cloneWrapper. - if ( this.dragComponentRef.current ) { + if ( dragComponentRef.current ) { // Position dragComponent at the same position as the cursor. - this.cloneWrapper.style.top = `${ event.clientY }px`; - this.cloneWrapper.style.left = `${ event.clientX }px`; + cloneWrapper.style.top = `${ event.clientY }px`; + cloneWrapper.style.left = `${ event.clientX }px`; + + const clonedDragComponent = ownerDocument.createElement( 'div' ); + clonedDragComponent.innerHTML = dragComponentRef.current.innerHTML; + cloneWrapper.appendChild( clonedDragComponent ); - const clonedDragComponent = document.createElement( 'div' ); - clonedDragComponent.innerHTML = this.dragComponentRef.current.innerHTML; - this.cloneWrapper.appendChild( clonedDragComponent ); + // Inject the cloneWrapper into the DOM. + ownerDocument.body.appendChild( cloneWrapper ); } else { + const element = ownerDocument.getElementById( elementId ); + + // Prepare element clone and append to element wrapper. + const elementRect = element.getBoundingClientRect(); + const elementWrapper = element.parentNode; + const elementTopOffset = parseInt( elementRect.top, 10 ); + const elementLeftOffset = parseInt( elementRect.left, 10 ); + + cloneWrapper.style.width = `${ + elementRect.width + clonePadding * 2 + }px`; + const clone = element.cloneNode( true ); clone.id = `clone-${ elementId }`; if ( elementRect.height > cloneHeightTransformationBreakpoint ) { // Scale down clone if original element is larger than 700px. - this.cloneWrapper.style.transform = 'scale(0.5)'; - this.cloneWrapper.style.transformOrigin = 'top left'; + cloneWrapper.style.transform = 'scale(0.5)'; + cloneWrapper.style.transformOrigin = 'top left'; // Position clone near the cursor. - this.cloneWrapper.style.top = `${ event.clientY - 100 }px`; - this.cloneWrapper.style.left = `${ event.clientX }px`; + cloneWrapper.style.top = `${ event.clientY - 100 }px`; + cloneWrapper.style.left = `${ event.clientX }px`; } else { // Position clone right over the original element (20px padding). - this.cloneWrapper.style.top = `${ + cloneWrapper.style.top = `${ elementTopOffset - clonePadding }px`; - this.cloneWrapper.style.left = `${ + cloneWrapper.style.left = `${ elementLeftOffset - clonePadding }px`; } @@ -164,68 +120,89 @@ class Draggable extends Component { clone.querySelectorAll( 'iframe' ) ).forEach( ( child ) => child.parentNode.removeChild( child ) ); - this.cloneWrapper.appendChild( clone ); - } + cloneWrapper.appendChild( clone ); - // Inject the cloneWrapper into the DOM. - elementWrapper.appendChild( this.cloneWrapper ); + // Inject the cloneWrapper into the DOM. + elementWrapper.appendChild( cloneWrapper ); + } // Mark the current cursor coordinates. - this.cursorLeft = event.clientX; - this.cursorTop = event.clientY; + let cursorLeft = event.clientX; + let cursorTop = event.clientY; + + function over( e ) { + cloneWrapper.style.top = `${ + parseInt( cloneWrapper.style.top, 10 ) + e.clientY - cursorTop + }px`; + cloneWrapper.style.left = `${ + parseInt( cloneWrapper.style.left, 10 ) + e.clientX - cursorLeft + }px`; + + // Update cursor coordinates. + cursorLeft = e.clientX; + cursorTop = e.clientY; + + if ( onDragOver ) { + onDragOver( e ); + } + } + + ownerDocument.addEventListener( 'dragover', over ); + // Update cursor to 'grabbing', document wide. - document.body.classList.add( 'is-dragging-components-draggable' ); - document.addEventListener( 'dragover', this.onDragOver ); + ownerDocument.body.classList.add( bodyClass ); // Allow the Synthetic Event to be accessed from asynchronous code. // https://reactjs.org/docs/events.html#event-pooling event.persist(); - this.props.setTimeout( onDragStart.bind( this, event ) ); - } - /** - * Cleans up drag state when drag has completed, or component unmounts - * while dragging. - */ - resetDragState() { - // Remove drag clone - document.removeEventListener( 'dragover', this.onDragOver ); - if ( this.cloneWrapper && this.cloneWrapper.parentNode ) { - this.cloneWrapper.parentNode.removeChild( this.cloneWrapper ); - this.cloneWrapper = null; + let timerId; + + if ( onDragStart ) { + timerId = setTimeout( () => onDragStart( event ) ); } - this.cursorLeft = null; - this.cursorTop = null; + cleanup.current = () => { + // Remove drag clone + if ( cloneWrapper && cloneWrapper.parentNode ) { + cloneWrapper.parentNode.removeChild( cloneWrapper ); + } - // Reset cursor. - document.body.classList.remove( 'is-dragging-components-draggable' ); - } + if ( dragImage && dragImage.parentNode ) { + dragImage.parentNode.removeChild( dragImage ); + } + + // Reset cursor. + ownerDocument.body.classList.remove( bodyClass ); + + ownerDocument.removeEventListener( 'dragover', over ); - render() { - const { - children, - __experimentalDragComponent: dragComponent, - } = this.props; - - return ( - <> - { children( { - onDraggableStart: this.onDragStart, - onDraggableEnd: this.onDragEnd, - } ) } - { dragComponent && ( - <div - className="components-draggable-drag-component-root" - style={ { display: 'none' } } - ref={ this.dragComponentRef } - > - { dragComponent } - </div> - ) } - </> - ); + clearTimeout( timerId ); + }; } -} -export default withSafeTimeout( Draggable ); + useEffect( + () => () => { + cleanup.current(); + }, + [] + ); + + return ( + <> + { children( { + onDraggableStart: start, + onDraggableEnd: end, + } ) } + { dragComponent && ( + <div + className="components-draggable-drag-component-root" + style={ { display: 'none' } } + ref={ dragComponentRef } + > + { dragComponent } + </div> + ) } + </> + ); +} diff --git a/packages/components/src/drop-zone/index.js b/packages/components/src/drop-zone/index.js index 8f247b7cefc10d..338d19b4f98e46 100644 --- a/packages/components/src/drop-zone/index.js +++ b/packages/components/src/drop-zone/index.js @@ -13,7 +13,7 @@ import { upload, Icon } from '@wordpress/icons'; /** * Internal dependencies */ -import { DropZoneConsumer, Context } from './provider'; +import { Context, INITIAL_DROP_ZONE_STATE } from './provider'; export function useDropZone( { element, @@ -22,14 +22,10 @@ export function useDropZone( { onDrop, isDisabled, withPosition, - __unstableIsRelative = false, + __unstableIsRelative: isRelative = false, } ) { - const { addDropZone, removeDropZone } = useContext( Context ); - const [ state, setState ] = useState( { - isDraggingOverDocument: false, - isDraggingOverElement: false, - type: null, - } ); + const dropZones = useContext( Context ); + const [ state, setState ] = useState( INITIAL_DROP_ZONE_STATE ); useEffect( () => { if ( ! isDisabled ) { @@ -40,31 +36,36 @@ export function useDropZone( { onHTMLDrop, setState, withPosition, - isRelative: __unstableIsRelative, + isRelative, }; - addDropZone( dropZone ); + dropZones.add( dropZone ); return () => { - removeDropZone( dropZone ); + dropZones.delete( dropZone ); }; } - }, [ isDisabled, onDrop, onFilesDrop, onHTMLDrop, withPosition ] ); + }, [ + isDisabled, + onDrop, + onFilesDrop, + onHTMLDrop, + withPosition, + isRelative, + ] ); - return state; -} + const { x, y, ...remainingState } = state; + let position = null; + + if ( x !== null && y !== null ) { + position = { x, y }; + } -const DropZone = ( props ) => ( - <DropZoneConsumer> - { ( { addDropZone, removeDropZone } ) => ( - <DropZoneComponent - addDropZone={ addDropZone } - removeDropZone={ removeDropZone } - { ...props } - /> - ) } - </DropZoneConsumer> -); + return { + ...remainingState, + position, + }; +} -function DropZoneComponent( { +export default function DropZoneComponent( { className, label, onFilesDrop, @@ -115,5 +116,3 @@ function DropZoneComponent( { </div> ); } - -export default DropZone; diff --git a/packages/components/src/drop-zone/provider.js b/packages/components/src/drop-zone/provider.js index 364f5491bb4afc..38cab06bd92d4c 100644 --- a/packages/components/src/drop-zone/provider.js +++ b/packages/components/src/drop-zone/provider.js @@ -1,27 +1,33 @@ /** * External dependencies */ -import { isEqual, find, some, filter, throttle, includes } from 'lodash'; +import { find, some, filter, includes, throttle } from 'lodash'; /** * WordPress dependencies */ -import { Component, createContext, createRef } from '@wordpress/element'; +import { + createContext, + useEffect, + useRef, + useContext, +} from '@wordpress/element'; +import { getFilesFromDataTransfer } from '@wordpress/dom'; import isShallowEqual from '@wordpress/is-shallow-equal'; -export const Context = createContext( { - addDropZone: () => {}, - removeDropZone: () => {}, -} ); +export const Context = createContext(); -const { Provider, Consumer } = Context; +const { Provider } = Context; -const getDragEventType = ( { dataTransfer } ) => { +function getDragEventType( { dataTransfer } ) { if ( dataTransfer ) { // Use lodash `includes` here as in the Edge browser `types` is implemented // as a DomStringList, whereas in other browsers it's an array. `includes` // happily works with both types. - if ( includes( dataTransfer.types, 'Files' ) ) { + if ( + includes( dataTransfer.types, 'Files' ) || + getFilesFromDataTransfer( dataTransfer ).length > 0 + ) { return 'file'; } @@ -31,17 +37,17 @@ const getDragEventType = ( { dataTransfer } ) => { } return 'default'; -}; +} -const isTypeSupportedByDropZone = ( type, dropZone ) => { - return ( +function isTypeSupportedByDropZone( type, dropZone ) { + return Boolean( ( type === 'file' && dropZone.onFilesDrop ) || - ( type === 'html' && dropZone.onHTMLDrop ) || - ( type === 'default' && dropZone.onDrop ) + ( type === 'html' && dropZone.onHTMLDrop ) || + ( type === 'default' && dropZone.onDrop ) ); -}; +} -const isWithinElementBounds = ( element, x, y ) => { +function isWithinElementBounds( element, x, y ) { const rect = element.getBoundingClientRect(); /// make sure the rect is a valid rect if ( rect.bottom === rect.top || rect.left === rect.right ) { @@ -51,233 +57,208 @@ const isWithinElementBounds = ( element, x, y ) => { return ( x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom ); -}; +} -class DropZoneProvider extends Component { - constructor() { - super( ...arguments ); - - // Event listeners - this.onDragOver = this.onDragOver.bind( this ); - this.onDrop = this.onDrop.bind( this ); - // Context methods so this component can receive data from consumers - this.addDropZone = this.addDropZone.bind( this ); - this.removeDropZone = this.removeDropZone.bind( this ); - // Utility methods - this.resetDragState = this.resetDragState.bind( this ); - this.toggleDraggingOverDocument = throttle( - this.toggleDraggingOverDocument.bind( this ), - 200 +function getPosition( event ) { + // In some contexts, it may be necessary to capture and redirect the + // drag event (e.g. atop an `iframe`). To accommodate this, you can + // create an instance of CustomEvent with the original event specified + // as the `detail` property. + // + // See: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events + const detail = + window.CustomEvent && event instanceof window.CustomEvent + ? event.detail + : event; + + return { x: detail.clientX, y: detail.clientY }; +} + +function getHoveredDropZone( dropZones, position, dragEventType ) { + const hoveredDropZones = filter( + Array.from( dropZones ), + ( dropZone ) => + isTypeSupportedByDropZone( dragEventType, dropZone ) && + isWithinElementBounds( + dropZone.element.current, + position.x, + position.y + ) + ); + + // Find the leaf dropzone not containing another dropzone + return find( hoveredDropZones, ( zone ) => { + const container = zone.isRelative + ? zone.element.current.parentElement + : zone.element.current; + + return ! some( + hoveredDropZones, + ( subZone ) => + subZone !== zone && + container.contains( subZone.element.current ) ); + } ); +} - this.dropZones = []; - this.dropZoneCallbacks = { - addDropZone: this.addDropZone, - removeDropZone: this.removeDropZone, - }; - this.state = { - hoveredDropZone: -1, - isDraggingOverDocument: false, - position: null, - }; - this.ref = createRef(); - } +export const INITIAL_DROP_ZONE_STATE = { + isDraggingOverDocument: false, + isDraggingOverElement: false, + x: null, + y: null, + type: null, +}; - componentDidMount() { - const { ownerDocument } = this.ref.current; - const { defaultView } = ownerDocument; - defaultView.addEventListener( 'dragover', this.onDragOver ); - defaultView.addEventListener( 'mouseup', this.resetDragState ); - // Note that `dragend` doesn't fire consistently for file and HTML drag - // events where the drag origin is outside the browser window. - // In Firefox it may also not fire if the originating node is removed. - defaultView.addEventListener( 'dragend', this.resetDragState ); - } +export function useDrop( ref ) { + const dropZones = useContext( Context ); - componentWillUnmount() { - const { ownerDocument } = this.ref.current; + useEffect( () => { + const { ownerDocument } = ref.current; const { defaultView } = ownerDocument; - defaultView.removeEventListener( 'dragover', this.onDragOver ); - defaultView.removeEventListener( 'mouseup', this.resetDragState ); - defaultView.removeEventListener( 'dragend', this.resetDragState ); - } - addDropZone( dropZone ) { - this.dropZones.push( dropZone ); - } + let lastRelative; - removeDropZone( dropZone ) { - this.dropZones = filter( this.dropZones, ( dz ) => dz !== dropZone ); - } + function updateDragZones( event ) { + if ( lastRelative && lastRelative.contains( event.target ) ) { + return; + } - resetDragState() { - // Avoid throttled drag over handler calls - this.toggleDraggingOverDocument.cancel(); + const dragEventType = getDragEventType( event ); + const position = getPosition( event ); + const hoveredDropZone = getHoveredDropZone( + dropZones, + position, + dragEventType + ); - const { isDraggingOverDocument, hoveredDropZone } = this.state; - if ( ! isDraggingOverDocument && hoveredDropZone === -1 ) { - return; - } + if ( hoveredDropZone && hoveredDropZone.isRelative ) { + lastRelative = hoveredDropZone.element.current.offsetParent; + } else { + lastRelative = null; + } - this.setState( { - hoveredDropZone: -1, - isDraggingOverDocument: false, - position: null, - } ); - - this.dropZones.forEach( ( dropZone ) => - dropZone.setState( { - isDraggingOverDocument: false, - isDraggingOverElement: false, - position: null, - type: null, - } ) - ); - } + // Notifying the dropzones + dropZones.forEach( ( dropZone ) => { + const isDraggingOverDropZone = dropZone === hoveredDropZone; + const newState = { + isDraggingOverDocument: isTypeSupportedByDropZone( + dragEventType, + dropZone + ), + isDraggingOverElement: isDraggingOverDropZone, + x: + isDraggingOverDropZone && dropZone.withPosition + ? position.x + : null, + y: + isDraggingOverDropZone && dropZone.withPosition + ? position.y + : null, + type: isDraggingOverDropZone ? dragEventType : null, + }; + + dropZone.setState( ( state ) => { + if ( isShallowEqual( state, newState ) ) { + return state; + } + + return newState; + } ); + } ); - toggleDraggingOverDocument( event, dragEventType ) { - // In some contexts, it may be necessary to capture and redirect the - // drag event (e.g. atop an `iframe`). To accommodate this, you can - // create an instance of CustomEvent with the original event specified - // as the `detail` property. - // - // See: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events - const detail = - window.CustomEvent && event instanceof window.CustomEvent - ? event.detail - : event; - - // Index of hovered dropzone. - const hoveredDropZones = filter( - this.dropZones, - ( dropZone ) => - isTypeSupportedByDropZone( dragEventType, dropZone ) && - isWithinElementBounds( - dropZone.element.current, - detail.clientX, - detail.clientY - ) - ); + event.preventDefault(); + } - // Find the leaf dropzone not containing another dropzone - const hoveredDropZone = find( hoveredDropZones, ( zone ) => { - const container = zone.isRelative - ? zone.element.current.parentElement - : zone.element.current; - - return ! some( - hoveredDropZones, - ( subZone ) => - subZone !== zone && - container.contains( subZone.element.current ) - ); - } ); + const throttledUpdateDragZones = throttle( updateDragZones, 200 ); - const hoveredDropZoneIndex = this.dropZones.indexOf( hoveredDropZone ); + function onDragOver( event ) { + throttledUpdateDragZones( event ); + event.preventDefault(); + } - let position = null; + function resetDragState() { + // Avoid throttled drag over handler calls + throttledUpdateDragZones.cancel(); - if ( hoveredDropZone && hoveredDropZone.withPosition ) { - position = { x: detail.clientX, y: detail.clientY }; + dropZones.forEach( ( dropZone ) => + dropZone.setState( INITIAL_DROP_ZONE_STATE ) + ); } - // Optimisation: Only update the changed dropzones - let toUpdate = []; + function onDrop( event ) { + // This seemingly useless line has been shown to resolve a Safari issue + // where files dragged directly from the dock are not recognized + event.dataTransfer && event.dataTransfer.files.length; // eslint-disable-line no-unused-expressions + + const dragEventType = getDragEventType( event ); + const position = getPosition( event ); + const hoveredDropZone = getHoveredDropZone( + dropZones, + position, + dragEventType + ); + + resetDragState(); - if ( ! this.state.isDraggingOverDocument ) { - toUpdate = this.dropZones; - } else if ( hoveredDropZoneIndex !== this.state.hoveredDropZone ) { - if ( this.state.hoveredDropZone !== -1 ) { - toUpdate.push( this.dropZones[ this.state.hoveredDropZone ] ); - } if ( hoveredDropZone ) { - toUpdate.push( hoveredDropZone ); + switch ( dragEventType ) { + case 'file': + hoveredDropZone.onFilesDrop( + getFilesFromDataTransfer( event.dataTransfer ), + position + ); + break; + case 'html': + hoveredDropZone.onHTMLDrop( + event.dataTransfer.getData( 'text/html' ), + position + ); + break; + case 'default': + hoveredDropZone.onDrop( event, position ); + } } - } else if ( - hoveredDropZone && - hoveredDropZoneIndex === this.state.hoveredDropZone && - ! isEqual( position, this.state.position ) - ) { - toUpdate.push( hoveredDropZone ); - } - - // Notifying the dropzones - toUpdate.forEach( ( dropZone ) => { - const index = this.dropZones.indexOf( dropZone ); - const isDraggingOverDropZone = index === hoveredDropZoneIndex; - dropZone.setState( { - isDraggingOverDocument: isTypeSupportedByDropZone( - dragEventType, - dropZone - ), - isDraggingOverElement: isDraggingOverDropZone, - position: isDraggingOverDropZone ? position : null, - type: isDraggingOverDropZone ? dragEventType : null, - } ); - } ); - const newState = { - isDraggingOverDocument: true, - hoveredDropZone: hoveredDropZoneIndex, - position, - }; - if ( ! isShallowEqual( newState, this.state ) ) { - this.setState( newState ); + event.stopPropagation(); + event.preventDefault(); } - } - onDragOver( event ) { - this.toggleDraggingOverDocument( event, getDragEventType( event ) ); - event.preventDefault(); - } + defaultView.addEventListener( 'drop', onDrop ); + defaultView.addEventListener( 'dragover', onDragOver ); + defaultView.addEventListener( 'mouseup', resetDragState ); + // Note that `dragend` doesn't fire consistently for file and HTML drag + // events where the drag origin is outside the browser window. + // In Firefox it may also not fire if the originating node is removed. + defaultView.addEventListener( 'dragend', resetDragState ); - onDrop( event ) { - // This seemingly useless line has been shown to resolve a Safari issue - // where files dragged directly from the dock are not recognized - event.dataTransfer && event.dataTransfer.files.length; // eslint-disable-line no-unused-expressions - - const { position, hoveredDropZone } = this.state; - const dragEventType = getDragEventType( event ); - const dropZone = this.dropZones[ hoveredDropZone ]; - this.resetDragState(); - - if ( dropZone ) { - switch ( dragEventType ) { - case 'file': - dropZone.onFilesDrop( - [ ...event.dataTransfer.files ], - position - ); - break; - case 'html': - dropZone.onHTMLDrop( - event.dataTransfer.getData( 'text/html' ), - position - ); - break; - case 'default': - dropZone.onDrop( event, position ); - } - } + return () => { + defaultView.removeEventListener( 'drop', onDrop ); + defaultView.removeEventListener( 'dragover', onDragOver ); + defaultView.removeEventListener( 'mouseup', resetDragState ); + defaultView.removeEventListener( 'dragend', resetDragState ); + }; + }, [ ref, dropZones ] ); +} - event.stopPropagation(); - event.preventDefault(); - } +export function DropZoneContextProvider( props ) { + const ref = useRef( new Set( [] ) ); + return <Provider { ...props } value={ ref.current } />; +} - render() { - return ( - <div - ref={ this.ref } - onDrop={ this.onDrop } - className="components-drop-zone__provider" - > - <Provider value={ this.dropZoneCallbacks }> - { this.props.children } - </Provider> - </div> - ); - } +function DropContainer( { children } ) { + const ref = useRef(); + useDrop( ref ); + return ( + <div ref={ ref } className="components-drop-zone__provider"> + { children } + </div> + ); } -export default DropZoneProvider; -export { Consumer as DropZoneConsumer }; +export default function DropZoneProvider( { children } ) { + return ( + <DropZoneContextProvider> + <DropContainer>{ children }</DropContainer> + </DropZoneContextProvider> + ); +} diff --git a/packages/components/src/dropdown-menu/index.js b/packages/components/src/dropdown-menu/index.js index 2525939403799d..c6e4c69436e18f 100644 --- a/packages/components/src/dropdown-menu/index.js +++ b/packages/components/src/dropdown-menu/index.js @@ -43,6 +43,7 @@ function DropdownMenu( { toggleProps, menuProps, disableOpenOnArrowDown = false, + text, // The following props exist for backward compatibility. menuLabel, position, @@ -129,6 +130,7 @@ function DropdownMenu( { aria-haspopup="true" aria-expanded={ isOpen } label={ label } + text={ text } showTooltip={ toggleProps?.showTooltip ?? true } > { mergedToggleProps.children } diff --git a/packages/components/src/dropdown-menu/style.scss b/packages/components/src/dropdown-menu/style.scss index 3de73e00afc1d8..1a83834eda570b 100644 --- a/packages/components/src/dropdown-menu/style.scss +++ b/packages/components/src/dropdown-menu/style.scss @@ -1,5 +1,5 @@ .components-dropdown-menu__popover .components-popover__content { - width: 200px; + min-width: 200px; } .components-dropdown-menu__menu { @@ -14,6 +14,7 @@ padding: 6px; outline: none; cursor: pointer; + white-space: nowrap; &.has-separator { margin-top: 6px; @@ -56,10 +57,6 @@ text-align: left; padding-left: $grid-unit-10; padding-right: $grid-unit-10; - - .components-menu-item__info-wrapper { - max-width: calc(100% - #{ $button-size-small + $grid-unit-10 }); - } } .components-menu-group { diff --git a/packages/components/src/focusable-iframe/index.js b/packages/components/src/focusable-iframe/index.js index 834beeef1ab907..dc554c8048a16b 100644 --- a/packages/components/src/focusable-iframe/index.js +++ b/packages/components/src/focusable-iframe/index.js @@ -1,65 +1,46 @@ -/** - * External dependencies - */ -import { omit } from 'lodash'; - /** * WordPress dependencies */ -import { Component, createRef } from '@wordpress/element'; -import { withGlobalEvents } from '@wordpress/compose'; - -/** - * Browser dependencies - */ - -const { FocusEvent } = window; - -class FocusableIframe extends Component { - constructor( props ) { - super( ...arguments ); - - this.checkFocus = this.checkFocus.bind( this ); - - this.node = props.iframeRef || createRef(); - } - - /** - * Checks whether the iframe is the activeElement, inferring that it has - * then received focus, and calls the `onFocus` prop callback. - */ - checkFocus() { - const iframe = this.node.current; - - if ( iframe.ownerDocument.activeElement !== iframe ) { - return; +import { useEffect, useRef } from '@wordpress/element'; + +export default function FocusableIframe( { iframeRef, onFocus, ...props } ) { + const fallbackRef = useRef(); + const ref = iframeRef || fallbackRef; + + useEffect( () => { + const iframe = ref.current; + const { ownerDocument } = iframe; + const { defaultView } = ownerDocument; + const { FocusEvent } = defaultView; + + /** + * Checks whether the iframe is the activeElement, inferring that it has + * then received focus, and calls the `onFocus` prop callback. + */ + function checkFocus() { + if ( ownerDocument.activeElement !== iframe ) { + return; + } + + const focusEvent = new FocusEvent( 'focus', { bubbles: true } ); + + iframe.dispatchEvent( focusEvent ); + + if ( onFocus ) { + onFocus( focusEvent ); + } } - const focusEvent = new FocusEvent( 'focus', { bubbles: true } ); - iframe.dispatchEvent( focusEvent ); + defaultView.addEventListener( 'blur', checkFocus ); - const { onFocus } = this.props; - if ( onFocus ) { - onFocus( focusEvent ); - } - } - - render() { - // Disable reason: The rendered iframe is a pass-through component, - // assigning props inherited from the rendering parent. It's the - // responsibility of the parent to assign a title. + return () => { + defaultView.removeEventListener( 'blur', checkFocus ); + }; + }, [ onFocus ] ); - /* eslint-disable jsx-a11y/iframe-has-title */ - return ( - <iframe - ref={ this.node } - { ...omit( this.props, [ 'iframeRef', 'onFocus' ] ) } - /> - ); - /* eslint-enable jsx-a11y/iframe-has-title */ - } + // Disable reason: The rendered iframe is a pass-through component, + // assigning props inherited from the rendering parent. It's the + // responsibility of the parent to assign a title. + // eslint-disable-next-line jsx-a11y/iframe-has-title + return <iframe ref={ ref } { ...props } />; } - -export default withGlobalEvents( { - blur: 'checkFocus', -} )( FocusableIframe ); diff --git a/packages/components/src/font-size-picker/README.md b/packages/components/src/font-size-picker/README.md index aa4e0ccefdb2f5..c1b6bcd70f9d97 100644 --- a/packages/components/src/font-size-picker/README.md +++ b/packages/components/src/font-size-picker/README.md @@ -68,7 +68,7 @@ If no value exists, this prop defines the starting position for the font size pi ### fontSizes An array of font size objects. The object should contain properties size, name, and slug. -The property `size` contains a number with the font size value, in `px`. +The property `size` contains a number with the font size value, in `px` or a string specifying the font size CSS property that should be used eg: "13px", "1em", or "clamp(12px, 5vw, 100px)". The `name` property includes a label for that font size e.g.: `Small`. The `slug` property is a string with a unique identifier for the font size. Used for the class generation process. @@ -89,7 +89,7 @@ If onChange is called without any parameter, it should reset the value, attendin The current font size value. -- Type: `Number` +- Type: `Number | String` - Required: No ### withSlider diff --git a/packages/components/src/font-size-picker/index.js b/packages/components/src/font-size-picker/index.js index a762aa979c591f..e2cdec0dcf522d 100644 --- a/packages/components/src/font-size-picker/index.js +++ b/packages/components/src/font-size-picker/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { isNumber, isString } from 'lodash'; + /** * WordPress dependencies */ @@ -16,12 +21,11 @@ import VisuallyHidden from '../visually-hidden'; const DEFAULT_FONT_SIZE = 'default'; const CUSTOM_FONT_SIZE = 'custom'; +const MAX_FONT_SIZE_DISPLAY = '25px'; function getSelectValueFromFontSize( fontSizes, value ) { if ( value ) { - const fontSizeValue = fontSizes.find( - ( font ) => font.size === Number( value ) - ); + const fontSizeValue = fontSizes.find( ( font ) => font.size === value ); return fontSizeValue ? fontSizeValue.slug : CUSTOM_FONT_SIZE; } return DEFAULT_FONT_SIZE; @@ -41,7 +45,10 @@ function getSelectOptions( optionsArray, disableCustomFontSizes ) { return optionsArray.map( ( option ) => ( { key: option.slug, name: option.name, - style: { fontSize: option.size }, + size: option.size, + style: { + fontSize: `min( ${ option.size }, ${ MAX_FONT_SIZE_DISPLAY } )`, + }, } ) ); } @@ -53,6 +60,20 @@ export default function FontSizePicker( { value, withSlider = false, } ) { + const hasUnits = + isString( value ) || + ( fontSizes[ 0 ] && isString( fontSizes[ 0 ].size ) ); + + let noUnitsValue; + if ( ! hasUnits ) { + noUnitsValue = value; + } else { + noUnitsValue = parseInt( value ); + } + + const isPixelValue = + isNumber( value ) || ( isString( value ) && value.endsWith( 'px' ) ); + const instanceId = useInstanceId( FontSizePicker ); const options = useMemo( @@ -81,10 +102,11 @@ export default function FontSizePicker( { ( option ) => option.key === selectedFontSizeSlug ) } onChange={ ( { selectedItem } ) => { - const selectedValue = - selectedItem.style && - selectedItem.style.fontSize; - onChange( Number( selectedValue ) ); + if ( hasUnits ) { + onChange( selectedItem.size ); + } else { + onChange( Number( selectedItem.size ) ); + } } } /> ) } @@ -99,10 +121,21 @@ export default function FontSizePicker( { type="number" min={ 1 } onChange={ ( event ) => { - onChange( Number( event.target.value ) ); + if ( + ! event.target.value && + event.target.value !== 0 + ) { + onChange( undefined ); + return; + } + if ( hasUnits ) { + onChange( event.target.value + 'px' ); + } else { + onChange( Number( event.target.value ) ); + } } } aria-label={ __( 'Custom' ) } - value={ value || '' } + value={ ( isPixelValue && noUnitsValue ) || '' } /> </div> ) } @@ -122,10 +155,10 @@ export default function FontSizePicker( { <RangeControl className="components-font-size-picker__custom-input" label={ __( 'Custom Size' ) } - value={ value || '' } + value={ ( isPixelValue && noUnitsValue ) || '' } initialPosition={ fallbackFontSize } onChange={ ( newValue ) => { - onChange( newValue ); + onChange( hasUnits ? newValue + 'px' : newValue ); } } min={ 12 } max={ 100 } diff --git a/packages/components/src/form-token-field/style.scss b/packages/components/src/form-token-field/style.scss index 7f68fa8b9dcbd9..1fcb039924219b 100644 --- a/packages/components/src/form-token-field/style.scss +++ b/packages/components/src/form-token-field/style.scss @@ -5,7 +5,7 @@ align-items: flex-start; width: 100%; margin: 0 0 $grid-unit-10 0; - padding: $grid-unit-05; + padding: $grid-unit-05/2 $grid-unit-05; cursor: text; @@ -21,16 +21,25 @@ // Token input input[type="text"].components-form-token-field__input { display: inline-block; + flex: 1; + font-size: 16px; width: 100%; max-width: 100%; margin-left: 4px; padding: 0; min-height: 24px; + min-width: 50px; background: inherit; border: 0; color: $gray-900; box-shadow: none; + // Resolves Zooming on iOS devices + // https://github.com/WordPress/gutenberg/issues/27405 + @include break-small() { + font-size: 13px; + } + &:focus, .components-form-token-field.is-active & { outline: none; @@ -133,13 +142,14 @@ line-height: 24px; height: auto; background: $gray-300; + min-width: unset; transition: all 0.2s cubic-bezier(0.4, 1, 0.4, 1); @include reduce-motion; } .components-form-token-field__token-text { - border-radius: 12px 0 0 12px; - padding: 0 4px 0 8px; + border-radius: 2px 0 0 2px; + padding: 0 0 0 8px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -147,9 +157,9 @@ .components-form-token-field__remove-token.components-button { cursor: pointer; - border-radius: 0 12px 12px 0; + border-radius: 0 2px 2px 0; padding: 0 2px; - color: $gray-700; + color: $gray-900; line-height: 10px; overflow: initial; diff --git a/packages/components/src/form-token-field/token.js b/packages/components/src/form-token-field/token.js index 37fbf536369195..bf6f9d814635c4 100644 --- a/packages/components/src/form-token-field/token.js +++ b/packages/components/src/form-token-field/token.js @@ -9,7 +9,7 @@ import { noop } from 'lodash'; */ import { useInstanceId } from '@wordpress/compose'; import { __, sprintf } from '@wordpress/i18n'; -import { closeCircleFilled } from '@wordpress/icons'; +import { closeSmall } from '@wordpress/icons'; /** * Internal dependencies @@ -70,7 +70,7 @@ export default function Token( { <Button className="components-form-token-field__remove-token" - icon={ closeCircleFilled } + icon={ closeSmall } onClick={ ! disabled && onClick } label={ messages.remove } aria-describedby={ `components-form-token-field__token-text-${ instanceId }` } diff --git a/packages/components/src/guide/finish-button.js b/packages/components/src/guide/finish-button.js index d6ee4ad552b069..538f60973c99f8 100644 --- a/packages/components/src/guide/finish-button.js +++ b/packages/components/src/guide/finish-button.js @@ -8,28 +8,19 @@ import { useRef, useLayoutEffect } from '@wordpress/element'; */ import Button from '../button'; -export default function FinishButton( { className, onClick, children } ) { - const button = useRef( null ); +export default function FinishButton( props ) { + const ref = useRef(); // Focus the button on mount if nothing else is focused. This prevents a // focus loss when the 'Next' button is swapped out. useLayoutEffect( () => { - if ( - ! document.activeElement || - document.activeElement === document.body - ) { - button.current.focus(); + const { ownerDocument } = ref.current; + const { activeElement, body } = ownerDocument; + + if ( ! activeElement || activeElement === body ) { + ref.current.focus(); } - }, [ button ] ); + }, [] ); - return ( - <Button - ref={ button } - className={ className } - isPrimary - onClick={ onClick } - > - { children } - </Button> - ); + return <Button { ...props } ref={ ref } />; } diff --git a/packages/components/src/guide/test/finish-button.js b/packages/components/src/guide/test/finish-button.js index 86e2d659f21b10..90c15b789ded79 100644 --- a/packages/components/src/guide/test/finish-button.js +++ b/packages/components/src/guide/test/finish-button.js @@ -30,7 +30,7 @@ describe( 'FinishButton', () => { it( 'receives focus on mount when nothing is focused', () => { const focus = jest.fn(); create( <FinishButton />, { - createNodeMock: () => ( { focus } ), + createNodeMock: () => ( { focus, ownerDocument: document } ), } ); expect( focus ).toHaveBeenCalled(); } ); @@ -42,7 +42,7 @@ describe( 'FinishButton', () => { const focus = jest.fn(); create( <FinishButton />, { - createNodeMock: () => ( { focus } ), + createNodeMock: () => ( { focus, ownerDocument: document } ), } ); expect( focus ).not.toHaveBeenCalled(); } ); diff --git a/packages/components/src/higher-order/navigate-regions/index.js b/packages/components/src/higher-order/navigate-regions/index.js index 3a3c5469e1f2c3..93ec95af801e0e 100644 --- a/packages/components/src/higher-order/navigate-regions/index.js +++ b/packages/components/src/higher-order/navigate-regions/index.js @@ -1,12 +1,7 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - /** * WordPress dependencies */ -import { useCallback, useState, useRef } from '@wordpress/element'; +import { useCallback, useState, useRef, useEffect } from '@wordpress/element'; import { createHigherOrderComponent, useKeyboardShortcut, @@ -18,56 +13,66 @@ const defaultShortcuts = { next: [ 'ctrl+`', rawShortcut.access( 'n' ) ], }; -export default createHigherOrderComponent( ( WrappedComponent ) => { - return ( { shortcuts = defaultShortcuts, ...props } ) => { - const container = useRef(); - const [ isFocusingRegions, setIsFocusingRegions ] = useState( false ); - const className = classnames( 'components-navigate-regions', { - 'is-focusing-regions': isFocusingRegions, - } ); +export function useNavigateRegions( ref, shortcuts = defaultShortcuts ) { + const [ isFocusingRegions, setIsFocusingRegions ] = useState( false ); + + function focusRegion( offset ) { + const regions = Array.from( + ref.current.querySelectorAll( '[role="region"]' ) + ); + if ( ! regions.length ) { + return; + } + let nextRegion = regions[ 0 ]; + const selectedIndex = regions.indexOf( + ref.current.ownerDocument.activeElement + ); + if ( selectedIndex !== -1 ) { + let nextIndex = selectedIndex + offset; + nextIndex = nextIndex === -1 ? regions.length - 1 : nextIndex; + nextIndex = nextIndex === regions.length ? 0 : nextIndex; + nextRegion = regions[ nextIndex ]; + } + + nextRegion.focus(); + setIsFocusingRegions( true ); + } + const focusPrevious = useCallback( () => focusRegion( -1 ), [] ); + const focusNext = useCallback( () => focusRegion( 1 ), [] ); - function focusRegion( offset ) { - const regions = Array.from( - container.current.querySelectorAll( '[role="region"]' ) - ); - if ( ! regions.length ) { - return; - } - let nextRegion = regions[ 0 ]; - const selectedIndex = regions.indexOf( - container.current.ownerDocument.activeElement - ); - if ( selectedIndex !== -1 ) { - let nextIndex = selectedIndex + offset; - nextIndex = nextIndex === -1 ? regions.length - 1 : nextIndex; - nextIndex = nextIndex === regions.length ? 0 : nextIndex; - nextRegion = regions[ nextIndex ]; - } + useKeyboardShortcut( shortcuts.previous, focusPrevious, { + bindGlobal: true, + } ); + useKeyboardShortcut( shortcuts.next, focusNext, { bindGlobal: true } ); - nextRegion.focus(); - setIsFocusingRegions( true ); + useEffect( () => { + function onClick() { + setIsFocusingRegions( false ); } - const focusPrevious = useCallback( () => focusRegion( -1 ), [ - container, - ] ); - const focusNext = useCallback( () => focusRegion( 1 ), [ container ] ); - useKeyboardShortcut( shortcuts.previous, focusPrevious, { - bindGlobal: true, - } ); - useKeyboardShortcut( shortcuts.next, focusNext, { bindGlobal: true } ); + ref.current.addEventListener( 'click', onClick ); + + return () => { + ref.current.removeEventListener( 'click', onClick ); + }; + }, [ setIsFocusingRegions ] ); + + if ( ! isFocusingRegions ) { + return; + } + + return 'is-focusing-regions'; +} - // Disable reason: Clicking the editor should dismiss the regions focus style - /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ +export default createHigherOrderComponent( + ( Component ) => ( { shortcuts, ...props } ) => { + const ref = useRef(); + const className = useNavigateRegions( ref, shortcuts ); return ( - <div - ref={ container } - className={ className } - onClick={ () => setIsFocusingRegions( false ) } - > - <WrappedComponent { ...props } /> + <div ref={ ref } className={ className }> + <Component { ...props } /> </div> ); - /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ - }; -}, 'navigateRegions' ); + }, + 'navigateRegions' +); diff --git a/packages/components/src/higher-order/navigate-regions/style.scss b/packages/components/src/higher-order/navigate-regions/style.scss index d57fef9bbb07fb..b34a4ef70fa986 100644 --- a/packages/components/src/higher-order/navigate-regions/style.scss +++ b/packages/components/src/higher-order/navigate-regions/style.scss @@ -1,6 +1,9 @@ -.components-navigate-regions.is-focusing-regions [role="region"] { +// Allow the position to be easily overridden to e.g. fixed. +[role="region"] { position: relative; +} +.is-focusing-regions [role="region"] { // For browsers that don't support outline-offset (IE11). &:focus::after { content: ""; diff --git a/packages/components/src/higher-order/with-constrained-tabbing/index.js b/packages/components/src/higher-order/with-constrained-tabbing/index.js index d3e372d8655de1..db1dbe27f6908c 100644 --- a/packages/components/src/higher-order/with-constrained-tabbing/index.js +++ b/packages/components/src/higher-order/with-constrained-tabbing/index.js @@ -1,69 +1,20 @@ /** * WordPress dependencies */ -import { Component, createRef } from '@wordpress/element'; -import { createHigherOrderComponent } from '@wordpress/compose'; -import { TAB } from '@wordpress/keycodes'; -import { focus } from '@wordpress/dom'; +import { + createHigherOrderComponent, + useConstrainedTabbing, +} from '@wordpress/compose'; const withConstrainedTabbing = createHigherOrderComponent( ( WrappedComponent ) => - class extends Component { - constructor() { - super( ...arguments ); - - this.focusContainRef = createRef(); - this.handleTabBehaviour = this.handleTabBehaviour.bind( this ); - } - - handleTabBehaviour( event ) { - if ( event.keyCode !== TAB ) { - return; - } - - const tabbables = focus.tabbable.find( - this.focusContainRef.current - ); - if ( ! tabbables.length ) { - return; - } - const firstTabbable = tabbables[ 0 ]; - const lastTabbable = tabbables[ tabbables.length - 1 ]; - - if ( event.shiftKey && event.target === firstTabbable ) { - event.preventDefault(); - lastTabbable.focus(); - } else if ( - ! event.shiftKey && - event.target === lastTabbable - ) { - event.preventDefault(); - firstTabbable.focus(); - /* - * When pressing Tab and none of the tabbables has focus, the keydown - * event happens on the wrapper div: move focus on the first tabbable. - */ - } else if ( ! tabbables.includes( event.target ) ) { - event.preventDefault(); - firstTabbable.focus(); - } - } - - render() { - // Disable reason: this component is non-interactive, but must capture - // events from the wrapped component to determine when the Tab key is used. - /* eslint-disable jsx-a11y/no-static-element-interactions */ - return ( - <div - onKeyDown={ this.handleTabBehaviour } - ref={ this.focusContainRef } - tabIndex="-1" - > - <WrappedComponent { ...this.props } /> - </div> - ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ - } + function ComponentWithConstrainedTabbing( props ) { + const ref = useConstrainedTabbing(); + return ( + <div ref={ ref } tabIndex="-1"> + <WrappedComponent { ...props } /> + </div> + ); }, 'withConstrainedTabbing' ); diff --git a/packages/components/src/higher-order/with-focus-outside/index.js b/packages/components/src/higher-order/with-focus-outside/index.js index ca966eb048af37..c83de77f009060 100644 --- a/packages/components/src/higher-order/with-focus-outside/index.js +++ b/packages/components/src/higher-order/with-focus-outside/index.js @@ -1,142 +1,33 @@ -/** - * External dependencies - */ -import { includes } from 'lodash'; - /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; -import { createHigherOrderComponent } from '@wordpress/compose'; - -/** - * Input types which are classified as button types, for use in considering - * whether element is a (focus-normalized) button. - * - * @type {string[]} - */ -const INPUT_BUTTON_TYPES = [ 'button', 'submit' ]; - -/** - * Returns true if the given element is a button element subject to focus - * normalization, or false otherwise. - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus - * - * @param {Element} element Element to test. - * - * @return {boolean} Whether element is a button. - */ -function isFocusNormalizedButton( element ) { - switch ( element.nodeName ) { - case 'A': - case 'BUTTON': - return true; - - case 'INPUT': - return includes( INPUT_BUTTON_TYPES, element.type ); - } - - return false; -} - -export default createHigherOrderComponent( ( WrappedComponent ) => { - return class extends Component { - constructor() { - super( ...arguments ); - - this.bindNode = this.bindNode.bind( this ); - this.cancelBlurCheck = this.cancelBlurCheck.bind( this ); - this.queueBlurCheck = this.queueBlurCheck.bind( this ); - this.normalizeButtonFocus = this.normalizeButtonFocus.bind( this ); - } - - componentWillUnmount() { - this.cancelBlurCheck(); - } - - bindNode( node ) { - if ( node ) { - this.node = node; - } else { - delete this.node; - this.cancelBlurCheck(); - } - } - - queueBlurCheck( event ) { - // React does not allow using an event reference asynchronously - // due to recycling behavior, except when explicitly persisted. - event.persist(); - - // Skip blur check if clicking button. See `normalizeButtonFocus`. - if ( this.preventBlurCheck ) { - return; - } - - this.blurCheckTimeout = setTimeout( () => { - // If document is not focused then focus should remain - // inside the wrapped component and therefore we cancel - // this blur event thereby leaving focus in place. - // https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus. - if ( ! document.hasFocus() ) { - event.preventDefault(); - return; - } - if ( 'function' === typeof this.node.handleFocusOutside ) { - this.node.handleFocusOutside( event ); - } - }, 0 ); - } - - cancelBlurCheck() { - clearTimeout( this.blurCheckTimeout ); - } - - /** - * Handles a mousedown or mouseup event to respectively assign and - * unassign a flag for preventing blur check on button elements. Some - * browsers, namely Firefox and Safari, do not emit a focus event on - * button elements when clicked, while others do. The logic here - * intends to normalize this as treating click on buttons as focus. - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus - * - * @param {MouseEvent} event Event for mousedown or mouseup. - */ - normalizeButtonFocus( event ) { - const { type, target } = event; - - const isInteractionEnd = includes( - [ 'mouseup', 'touchend' ], - type - ); - - if ( isInteractionEnd ) { - this.preventBlurCheck = false; - } else if ( isFocusNormalizedButton( target ) ) { - this.preventBlurCheck = true; - } - } - - render() { - // Disable reason: See `normalizeButtonFocus` for browser-specific - // focus event normalization. - - /* eslint-disable jsx-a11y/no-static-element-interactions */ - return ( - <div - onFocus={ this.cancelBlurCheck } - onMouseDown={ this.normalizeButtonFocus } - onMouseUp={ this.normalizeButtonFocus } - onTouchStart={ this.normalizeButtonFocus } - onTouchEnd={ this.normalizeButtonFocus } - onBlur={ this.queueBlurCheck } - > - <WrappedComponent ref={ this.bindNode } { ...this.props } /> - </div> - ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ - } - }; -}, 'withFocusOutside' ); +import { useCallback, useState } from '@wordpress/element'; +import { + createHigherOrderComponent, + __experimentalUseFocusOutside as useFocusOutside, +} from '@wordpress/compose'; + +export default createHigherOrderComponent( + ( WrappedComponent ) => ( props ) => { + const [ handleFocusOutside, setHandleFocusOutside ] = useState(); + const bindFocusOutsideHandler = useCallback( + ( node ) => + setHandleFocusOutside( () => + node?.handleFocusOutside + ? node.handleFocusOutside.bind( node ) + : undefined + ), + [] + ); + + return ( + <div { ...useFocusOutside( handleFocusOutside ) }> + <WrappedComponent + ref={ bindFocusOutsideHandler } + { ...props } + /> + </div> + ); + }, + 'withFocusOutside' +); diff --git a/packages/components/src/higher-order/with-focus-outside/index.native.js b/packages/components/src/higher-order/with-focus-outside/index.native.js index 98249c09fbdd22..e23ae14b0b52de 100644 --- a/packages/components/src/higher-order/with-focus-outside/index.native.js +++ b/packages/components/src/higher-order/with-focus-outside/index.native.js @@ -1,133 +1,37 @@ /** * External dependencies */ -import { includes } from 'lodash'; import { View } from 'react-native'; - /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; -import { createHigherOrderComponent } from '@wordpress/compose'; - -/** - * Input types which are classified as button types, for use in considering - * whether element is a (focus-normalized) button. - * - * @type {string[]} - */ -const INPUT_BUTTON_TYPES = [ 'button', 'submit' ]; - -/** - * Returns true if the given element is a button element subject to focus - * normalization, or false otherwise. - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus - * - * @param {Element} element Element to test. - * - * @return {boolean} Whether element is a button. - */ -function isFocusNormalizedButton( element ) { - switch ( element.nodeName ) { - case 'A': - case 'BUTTON': - return true; - - case 'INPUT': - return includes( INPUT_BUTTON_TYPES, element.type ); - } - - return false; -} - -export default createHigherOrderComponent( ( WrappedComponent ) => { - return class extends Component { - constructor() { - super( ...arguments ); - - this.bindNode = this.bindNode.bind( this ); - this.cancelBlurCheck = this.cancelBlurCheck.bind( this ); - this.queueBlurCheck = this.queueBlurCheck.bind( this ); - this.normalizeButtonFocus = this.normalizeButtonFocus.bind( this ); - } - - componentWillUnmount() { - this.cancelBlurCheck(); - } - - bindNode( node ) { - if ( node ) { - this.node = node; - } else { - delete this.node; - this.cancelBlurCheck(); - } - } - - queueBlurCheck( event ) { - // React does not allow using an event reference asynchronously - // due to recycling behavior, except when explicitly persisted. - event.persist(); - - // Skip blur check if clicking button. See `normalizeButtonFocus`. - if ( this.preventBlurCheck ) { - return; - } - - this.blurCheckTimeout = setTimeout( () => { - if ( 'function' === typeof this.node.handleFocusOutside ) { - this.node.handleFocusOutside( event ); - } - }, 0 ); - } - - cancelBlurCheck() { - clearTimeout( this.blurCheckTimeout ); - } - - /** - * Handles a mousedown or mouseup event to respectively assign and - * unassign a flag for preventing blur check on button elements. Some - * browsers, namely Firefox and Safari, do not emit a focus event on - * button elements when clicked, while others do. The logic here - * intends to normalize this as treating click on buttons as focus. - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus - * - * @param {MouseEvent} event Event for mousedown or mouseup. - */ - normalizeButtonFocus( event ) { - const { type, target } = event; - - const isInteractionEnd = includes( - [ 'mouseup', 'touchend' ], - type - ); - - if ( isInteractionEnd ) { - this.preventBlurCheck = false; - } else if ( isFocusNormalizedButton( target ) ) { - this.preventBlurCheck = true; - } - } - - render() { - // Disable reason: See `normalizeButtonFocus` for browser-specific - // focus event normalization. - - return ( - <View - onFocus={ this.cancelBlurCheck } - onMouseDown={ this.normalizeButtonFocus } - onMouseUp={ this.normalizeButtonFocus } - onTouchStart={ this.normalizeButtonFocus } - onTouchEnd={ this.normalizeButtonFocus } - onBlur={ this.queueBlurCheck } - > - <WrappedComponent ref={ this.bindNode } { ...this.props } /> - </View> - ); - } - }; -}, 'withFocusOutside' ); +import { useCallback, useState } from '@wordpress/element'; +import { + createHigherOrderComponent, + __experimentalUseFocusOutside as useFocusOutside, +} from '@wordpress/compose'; + +export default createHigherOrderComponent( + ( WrappedComponent ) => ( props ) => { + const [ handleFocusOutside, setHandleFocusOutside ] = useState(); + const bindFocusOutsideHandler = useCallback( + ( node ) => + setHandleFocusOutside( () => + node?.handleFocusOutside + ? node.handleFocusOutside.bind( node ) + : undefined + ), + [] + ); + + return ( + <View { ...useFocusOutside( handleFocusOutside ) }> + <WrappedComponent + ref={ bindFocusOutsideHandler } + { ...props } + /> + </View> + ); + }, + 'withFocusOutside' +); diff --git a/packages/components/src/higher-order/with-focus-return/README.md b/packages/components/src/higher-order/with-focus-return/README.md index 13b9053026e468..4521781eec989b 100644 --- a/packages/components/src/higher-order/with-focus-return/README.md +++ b/packages/components/src/higher-order/with-focus-return/README.md @@ -2,8 +2,6 @@ `withFocusReturn` is a higher-order component used typically in scenarios of short-lived elements (modals, dropdowns) where, upon the element's unmounting, focus should be restored to the focused element which had initiated it being rendered. -Optionally, it can be used in combination with a `FocusReturnProvider` which, when rendered toward the top of an application, will remember a history of elements focused during a session. This can provide safeguards for scenarios where one short-lived element triggers the creation of another (e.g. a dropdown menu triggering a modal display). The combined effect of `FocusReturnProvider` and `withFocusReturn` is that focus will be returned to the most recent focused element which is still present in the document. - ## Usage ### `withFocusReturn` @@ -71,17 +69,3 @@ const EnhancedMyComponent = withFocusReturn( { }, } )( MyComponent ); ``` - -### `FocusReturnProvider` - -```jsx -import { FocusReturnProvider } from '@wordpress/components'; - -function App() { - return ( - <FocusReturnProvider> - { /* ... */ } - </FocusReturnProvider> - ); -} -``` diff --git a/packages/components/src/higher-order/with-focus-return/context.js b/packages/components/src/higher-order/with-focus-return/context.js deleted file mode 100644 index f65834f4886927..00000000000000 --- a/packages/components/src/higher-order/with-focus-return/context.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * External dependencies - */ -import { uniq } from 'lodash'; - -/** - * WordPress dependencies - */ -import { Component, createContext } from '@wordpress/element'; - -const { Provider, Consumer } = createContext( { - focusHistory: [], -} ); - -Provider.displayName = 'FocusReturnProvider'; -Consumer.displayName = 'FocusReturnConsumer'; - -/** - * The maximum history length to capture for the focus stack. When exceeded, - * items should be shifted from the stack for each consecutive push. - * - * @type {number} - */ -const MAX_STACK_LENGTH = 100; - -class FocusReturnProvider extends Component { - constructor() { - super( ...arguments ); - - this.onFocus = this.onFocus.bind( this ); - - this.state = { - focusHistory: [], - }; - } - - onFocus( event ) { - const { focusHistory } = this.state; - - // Push the focused element to the history stack, keeping only unique - // members but preferring the _last_ occurrence of any duplicates. - // Lodash's `uniq` behavior favors the first occurrence, so the array - // is temporarily reversed prior to it being called upon. Uniqueness - // helps avoid situations where, such as in a constrained tabbing area, - // the user changes focus enough within a transient element that the - // stack may otherwise only consist of members pending destruction, at - // which point focus might have been lost. - const nextFocusHistory = uniq( - [ ...focusHistory, event.target ] - .slice( -1 * MAX_STACK_LENGTH ) - .reverse() - ).reverse(); - - this.setState( { focusHistory: nextFocusHistory } ); - } - - render() { - const { children, className } = this.props; - - return ( - <Provider value={ this.state }> - <div onFocus={ this.onFocus } className={ className }> - { children } - </div> - </Provider> - ); - } -} - -export default FocusReturnProvider; -export { Consumer }; diff --git a/packages/components/src/higher-order/with-focus-return/index.js b/packages/components/src/higher-order/with-focus-return/index.js index 6b12b693899a2e..c2a5db9a87a2ed 100644 --- a/packages/components/src/higher-order/with-focus-return/index.js +++ b/packages/components/src/higher-order/with-focus-return/index.js @@ -1,18 +1,9 @@ -/** - * External dependencies - */ -import { stubTrue, without } from 'lodash'; - /** * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { createHigherOrderComponent } from '@wordpress/compose'; - -/** - * Internal dependencies - */ -import Provider, { Consumer } from './context'; +import { createHigherOrderComponent, useFocusReturn } from '@wordpress/compose'; +import deprecated from '@wordpress/deprecated'; /** * Returns true if the given object is component-like. An object is component- @@ -37,90 +28,35 @@ function isComponentLike( object ) { * describing the component and the * focus return characteristics. * - * @return {WPComponent} Component with the focus restauration behaviour. + * @return {Function} Higher Order Component with the focus restauration behaviour. */ -function withFocusReturn( options ) { - // Normalize as overloaded form `withFocusReturn( options )( Component )` - // or as `withFocusReturn( Component )`. +export default createHigherOrderComponent( ( options ) => { + const HoC = ( { onFocusReturn } = {} ) => ( WrappedComponent ) => { + const WithFocusReturn = ( props ) => { + const ref = useFocusReturn( onFocusReturn ); + return ( + <div ref={ ref }> + <WrappedComponent { ...props } /> + </div> + ); + }; + + return WithFocusReturn; + }; + if ( isComponentLike( options ) ) { const WrappedComponent = options; - return withFocusReturn( {} )( WrappedComponent ); + return HoC()( WrappedComponent ); } - const { onFocusReturn = stubTrue } = options; - - return ( WrappedComponent ) => { - class FocusReturn extends Component { - constructor() { - super( ...arguments ); + return HoC( options ); +}, 'withFocusReturn' ); - this.ownFocusedElements = new Set(); - this.activeElementOnMount = document.activeElement; - this.setIsFocusedFalse = () => ( this.isFocused = false ); - this.setIsFocusedTrue = ( event ) => { - this.ownFocusedElements.add( event.target ); - this.isFocused = true; - }; - } - - componentWillUnmount() { - const { - activeElementOnMount, - isFocused, - ownFocusedElements, - } = this; - - if ( ! isFocused ) { - return; - } - - // Defer to the component's own explicit focus return behavior, - // if specified. The function should return `false` to prevent - // the default behavior otherwise occurring here. This allows - // for support that the `onFocusReturn` decides to allow the - // default behavior to occur under some conditions. - if ( onFocusReturn() === false ) { - return; - } - - const stack = [ - ...without( - this.props.focus.focusHistory, - ...ownFocusedElements - ), - activeElementOnMount, - ]; - - let candidate; - while ( ( candidate = stack.pop() ) ) { - if ( document.body.contains( candidate ) ) { - candidate.focus(); - return; - } - } - } - - render() { - return ( - <div - onFocus={ this.setIsFocusedTrue } - onBlur={ this.setIsFocusedFalse } - > - <WrappedComponent { ...this.props.childProps } /> - </div> - ); - } - } - - return ( props ) => ( - <Consumer> - { ( context ) => ( - <FocusReturn childProps={ props } focus={ context } /> - ) } - </Consumer> - ); - }; -} +export const Provider = ( { children } ) => { + deprecated( 'wp.components.FocusReturnProvider component', { + hint: + 'This provider is not used anymore. You can just remove it from your codebase', + } ); -export default createHigherOrderComponent( withFocusReturn, 'withFocusReturn' ); -export { Provider }; + return children; +}; diff --git a/packages/components/src/higher-order/with-focus-return/test/index.js b/packages/components/src/higher-order/with-focus-return/test/index.js index 67502237260b32..07f8000de99311 100644 --- a/packages/components/src/higher-order/with-focus-return/test/index.js +++ b/packages/components/src/higher-order/with-focus-return/test/index.js @@ -2,7 +2,7 @@ * External dependencies */ import renderer from 'react-test-renderer'; -import { render } from '@testing-library/react'; +import { render, fireEvent } from '@testing-library/react'; /** * WordPress dependencies @@ -12,7 +12,7 @@ import { Component } from '@wordpress/element'; /** * Internal dependencies */ -import withFocusReturn, { Provider } from '../'; +import withFocusReturn from '../'; class Test extends Component { render() { @@ -93,55 +93,16 @@ describe( 'withFocusReturn()', () => { } ); const textarea = container.querySelector( 'textarea' ); + fireEvent.focusIn( textarea, { target: textarea } ); textarea.focus(); expect( document.activeElement ).toBe( textarea ); // Should return to the activeElement saved with this component. unmount(); + render( <div></div>, { + container, + } ); expect( document.activeElement ).toBe( activeElement ); } ); - - it( 'should switch focus to the most recent still-available focus target', () => { - const TestComponent = ( props ) => ( - <Provider> - <input name="first" /> - { props.renderSecondInput && <input name="second" /> } - { props.renderComposite && <Composite /> } - </Provider> - ); - - const { container, rerender } = render( - <TestComponent renderSecondInput />, - { - container: document.body.appendChild( - document.createElement( 'div' ) - ), - } - ); - - const firstInput = container.querySelector( 'input[name="first"]' ); - firstInput.focus(); - - const secondInput = container.querySelector( - 'input[name="second"]' - ); - secondInput.focus(); - - expect( document.activeElement ).toBe( secondInput ); - - rerender( <TestComponent renderSecondInput renderComposite /> ); - const textarea = container.querySelector( 'textarea' ); - textarea.focus(); - - expect( document.activeElement ).toBe( textarea ); - - rerender( <TestComponent renderComposite /> ); - - expect( document.activeElement ).toBe( textarea ); - - rerender( <TestComponent /> ); - - expect( document.activeElement ).toBe( firstInput ); - } ); } ); } ); diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 00270b85823a61..aaf2620e259237 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -14,7 +14,7 @@ export { export { default as __experimentalAlignmentMatrixControl } from './alignment-matrix-control'; export { default as Animate, - useAnimate as __unstableUseAnimate, + getAnimateClassName as __unstableGetAnimateClassName, } from './animate'; export { default as AnglePickerControl } from './angle-picker-control'; export { default as Autocomplete } from './autocomplete'; @@ -37,7 +37,7 @@ export { default as ColorPicker } from './color-picker'; export { default as ComboboxControl } from './combobox-control'; export { default as CustomSelectControl } from './custom-select-control'; export { default as Dashicon } from './dashicon'; -export { DateTimePicker, DatePicker, TimePicker } from './date-time'; +export { default as DateTimePicker, DatePicker, TimePicker } from './date-time'; export { default as __experimentalDimensionControl } from './dimension-control'; export { default as Disabled } from './disabled'; export { default as Draggable } from './draggable'; @@ -45,7 +45,11 @@ export { default as DropZone, useDropZone as __unstableUseDropZone, } from './drop-zone'; -export { default as DropZoneProvider } from './drop-zone/provider'; +export { + default as DropZoneProvider, + DropZoneContextProvider as __unstableDropZoneContextProvider, + useDrop as __unstableUseDrop, +} from './drop-zone/provider'; export { default as Dropdown } from './dropdown'; export { default as DropdownMenu } from './dropdown-menu'; export { default as ExternalLink } from './external-link'; @@ -130,7 +134,10 @@ export { } from './slot-fill'; // Higher-Order Components -export { default as navigateRegions } from './higher-order/navigate-regions'; +export { + default as navigateRegions, + useNavigateRegions as __unstableUseNavigateRegions, +} from './higher-order/navigate-regions'; export { default as withConstrainedTabbing } from './higher-order/with-constrained-tabbing'; export { default as withFallbackStyles } from './higher-order/with-fallback-styles'; export { default as withFilters } from './higher-order/with-filters'; diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js index 5b963a20dc33ac..ccfdaf84e17be1 100644 --- a/packages/components/src/index.native.js +++ b/packages/components/src/index.native.js @@ -45,6 +45,7 @@ export { default as QueryControls } from './query-controls'; export { default as Notice } from './notice'; export { default as NoticeList } from './notice/list'; export { default as RadioControl } from './radio-control'; +export { default as UnitControl } from './unit-control'; // Higher-Order Components export { default as withConstrainedTabbing } from './higher-order/with-constrained-tabbing'; @@ -73,7 +74,10 @@ export { default as CycleSelectControl } from './mobile/cycle-select-control'; export { default as Gradient } from './mobile/gradient'; export { default as ColorSettings } from './mobile/color-settings'; export { LinkPicker } from './mobile/link-picker'; +export { default as LinkPickerScreen } from './mobile/link-picker/link-picker-screen'; export { default as LinkSettings } from './mobile/link-settings'; +export { default as LinkSettingsScreen } from './mobile/link-settings/link-settings-screen'; +export { default as LinkSettingsNavigation } from './mobile/link-settings/link-settings-navigation'; export { default as Image, IMAGE_DEFAULT_FOCAL_POINT } from './mobile/image'; export { default as ImageEditingButton } from './mobile/image/image-editing-button'; export { default as InserterButton } from './mobile/inserter-button'; @@ -85,8 +89,15 @@ export { ALIGNMENT_BREAKPOINTS, } from './mobile/utils/alignments'; +// Hooks +export { + useConvertUnitToMobile, + getValueAndUnit, +} from './mobile/utils/use-unit-converter-to-mobile'; + export { default as GlobalStylesContext, useGlobalStyles, withGlobalStyles, + getMergedGlobalStyles, } from './mobile/global-styles-context'; diff --git a/packages/components/src/isolated-event-container/README.md b/packages/components/src/isolated-event-container/README.md index 90ce7624f41679..a14b9bd2f9bce0 100644 --- a/packages/components/src/isolated-event-container/README.md +++ b/packages/components/src/isolated-event-container/README.md @@ -1,5 +1,7 @@ # Isolated Event Container +**Deprecated** + This is a container that prevents certain events from propagating outside of the container. This is used to wrap UI elements such as modals and popovers where the propagated event can cause problems. The event continues to work inside the component. diff --git a/packages/components/src/isolated-event-container/index.js b/packages/components/src/isolated-event-container/index.js index 83a5ad4725d614..d3513953dd1314 100644 --- a/packages/components/src/isolated-event-container/index.js +++ b/packages/components/src/isolated-event-container/index.js @@ -2,12 +2,15 @@ * WordPress dependencies */ import { forwardRef } from '@wordpress/element'; +import deprecated from '@wordpress/deprecated'; function stopPropagation( event ) { event.stopPropagation(); } export default forwardRef( ( { children, ...props }, ref ) => { + deprecated( 'wp.components.IsolatedEventContainer' ); + // Disable reason: this stops certain events from propagating outside of the component. // - onMouseDown is disabled as this can cause interactions with other DOM elements /* eslint-disable jsx-a11y/no-static-element-interactions */ diff --git a/packages/components/src/isolated-event-container/test/index.js b/packages/components/src/isolated-event-container/test/index.js index a629ce9bfdd887..9a83885a61c1bf 100644 --- a/packages/components/src/isolated-event-container/test/index.js +++ b/packages/components/src/isolated-event-container/test/index.js @@ -16,6 +16,7 @@ describe( 'IsolatedEventContainer', () => { expect( isolated.hasClass( 'test' ) ).toBe( true ); expect( isolated.prop( 'onClick' ) ).toBe( 'click' ); + expect( console ).toHaveWarned(); } ); it( 'should stop mousedown event propagation', () => { diff --git a/packages/components/src/menu-item/index.js b/packages/components/src/menu-item/index.js index d685606102669b..8bed60d84df8f7 100644 --- a/packages/components/src/menu-item/index.js +++ b/packages/components/src/menu-item/index.js @@ -49,7 +49,7 @@ export function MenuItem( if ( info ) { children = ( <span className="components-menu-item__info-wrapper"> - { children } + <span className="components-menu-item__item">{ children }</span> <span className="components-menu-item__info">{ info }</span> </span> ); @@ -74,7 +74,7 @@ export function MenuItem( className={ className } { ...props } > - { children } + <span className="components-menu-item__item">{ children }</span> <Shortcut className="components-menu-item__shortcut" shortcut={ shortcut } diff --git a/packages/components/src/menu-item/style.scss b/packages/components/src/menu-item/style.scss index e2c30395dc9042..d707dcd627cca6 100644 --- a/packages/components/src/menu-item/style.scss +++ b/packages/components/src/menu-item/style.scss @@ -4,7 +4,7 @@ .components-menu-items__item-icon { margin-right: -2px; // This optically balances the icon. - margin-left: auto; + margin-left: $grid-unit-30; display: inline-block; flex: 0 0 auto; } @@ -23,19 +23,28 @@ .components-menu-item__info-wrapper { display: flex; flex-direction: column; + margin-right: auto; } .components-menu-item__info { margin-top: $grid-unit-05; font-size: $helptext-font-size; color: $gray-700; + white-space: normal; +} + +.components-menu-item__item { + white-space: nowrap; + margin-right: auto; + display: inline-flex; + align-items: center; } .components-menu-item__shortcut { align-self: center; margin-right: 0; margin-left: auto; - padding-left: $grid-unit-15; + padding-left: $grid-unit-30; color: currentColor; // Hide the keyboard shortcuts on mobile, because they aren't super-useful diff --git a/packages/components/src/menu-item/test/__snapshots__/index.js.snap b/packages/components/src/menu-item/test/__snapshots__/index.js.snap index e2a9fb4bd470b0..2349726c1d07a5 100644 --- a/packages/components/src/menu-item/test/__snapshots__/index.js.snap +++ b/packages/components/src/menu-item/test/__snapshots__/index.js.snap @@ -7,7 +7,11 @@ exports[`MenuItem should match snapshot when all props provided 1`] = ` onClick={[Function]} role="menuitemcheckbox" > - My item + <span + className="components-menu-item__item" + > + My item + </span> <Shortcut className="components-menu-item__shortcut" shortcut="mod+shift+alt+w" @@ -34,13 +38,21 @@ exports[`MenuItem should match snapshot when info is provided 1`] = ` role="menuitem" > <span - className="components-menu-item__info-wrapper" + className="components-menu-item__item" > - My item <span - className="components-menu-item__info" + className="components-menu-item__info-wrapper" > - Extended description of My Item + <span + className="components-menu-item__item" + > + My item + </span> + <span + className="components-menu-item__info" + > + Extended description of My Item + </span> </span> </span> <Shortcut @@ -55,7 +67,11 @@ exports[`MenuItem should match snapshot when isSelected and role are optionally onClick={[Function]} role="menuitem" > - My item + <span + className="components-menu-item__item" + > + My item + </span> <Shortcut className="components-menu-item__shortcut" shortcut="mod+shift+alt+w" @@ -81,7 +97,11 @@ exports[`MenuItem should match snapshot when only label provided 1`] = ` className="components-menu-item__button" role="menuitem" > - My item + <span + className="components-menu-item__item" + > + My item + </span> <Shortcut className="components-menu-item__shortcut" /> diff --git a/packages/components/src/menu-items-choice/index.js b/packages/components/src/menu-items-choice/index.js index 299eb2b743f198..617e44161526f6 100644 --- a/packages/components/src/menu-items-choice/index.js +++ b/packages/components/src/menu-items-choice/index.js @@ -26,6 +26,7 @@ export default function MenuItemsChoice( { key={ item.value } role="menuitemradio" icon={ isSelected && check } + info={ item.info } isSelected={ isSelected } shortcut={ item.shortcut } className="components-menu-items-choice" diff --git a/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/README.md b/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/README.md index 4ad61cc91f66d8..0ac10136284dc2 100644 --- a/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/README.md +++ b/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/README.md @@ -22,7 +22,7 @@ import { BottomSheet } from '@wordpress/components'; const BottomSheetWithNavigation = () => ( -  <BottomSheet> +  <BottomSheet hasNavigation>     <BottomSheet.NavigationContainer>       <BottomSheet.NavigationScreen         name={ 'Settings' } @@ -85,4 +85,18 @@ This prop is used as a Screen name. The component that should be rendered as content. - Type: React Element -- Required: Yes \ No newline at end of file +- Required: Yes + +### isScrollable + +This prop determines whether the screen should be wrapped into the ScrollView - this is needed if the screen contains FlatList or any other list inside. Thanks to that we do not nest List into the ScrollView with the same orientation. + +- Type: `Boolean` +- Required: No + +### fullScreen + +This prop determines if the screen should have full height of device. + +- Type: `Boolean` +- Required: No \ No newline at end of file diff --git a/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/navigation-container.native.js b/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/navigation-container.native.js index 26c66a2d89e010..afa238b144d0c9 100644 --- a/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/navigation-container.native.js +++ b/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/navigation-container.native.js @@ -15,6 +15,7 @@ import { useCallback, Children, useRef, + cloneElement, } from '@wordpress/element'; import { usePreferredColorSchemeStyle } from '@wordpress/compose'; @@ -108,12 +109,19 @@ function BottomSheetNavigationContainer( { children, animate, main, theme } ) { const screens = useMemo( () => { return Children.map( children, ( child ) => { + let screen = child; const { name, ...otherProps } = child.props; + if ( ! main ) { + screen = cloneElement( child, { + ...child.props, + isNested: true, + } ); + } return ( <Stack.Screen name={ name } { ...otherProps } - children={ () => child } + children={ () => screen } /> ); } ); diff --git a/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/navigation-screen.native.js b/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/navigation-screen.native.js index b29f18ee86f512..af802a96e0f436 100644 --- a/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/navigation-screen.native.js +++ b/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/navigation-screen.native.js @@ -6,7 +6,7 @@ import { useNavigation, useFocusEffect, } from '@react-navigation/native'; -import { View } from 'react-native'; +import { View, ScrollView, TouchableHighlight } from 'react-native'; import { debounce } from 'lodash'; /** @@ -20,8 +20,14 @@ import { useRef, useCallback, useContext, useMemo } from '@wordpress/element'; * Internal dependencies */ import { BottomSheetNavigationContext } from './bottom-sheet-navigation-context'; +import styles from './styles.scss'; -const BottomSheetNavigationScreen = ( { children, fullScreen } ) => { +const BottomSheetNavigationScreen = ( { + children, + fullScreen, + isScrollable, + isNested, +} ) => { const navigation = useNavigation(); const heightRef = useRef( { maxHeight: 0 } ); const isFocused = useIsFocused(); @@ -29,6 +35,8 @@ const BottomSheetNavigationScreen = ( { children, fullScreen } ) => { onHandleHardwareButtonPress, shouldEnableBottomSheetMaxHeight, setIsFullScreen, + listProps, + safeAreaBottomInset, } = useContext( BottomSheetContext ); const { setHeight } = useContext( BottomSheetNavigationContext ); @@ -56,7 +64,7 @@ const BottomSheetNavigationScreen = ( { children, fullScreen } ) => { setHeight( heightRef.current.maxHeight ); } return () => {}; - }, [ setHeight ] ) + }, [] ) ); const onLayout = ( { nativeEvent } ) => { if ( fullScreen ) { @@ -69,10 +77,28 @@ const BottomSheetNavigationScreen = ( { children, fullScreen } ) => { setHeightDebounce( height ); } }; - return useMemo( () => { - return <View onLayout={ onLayout }>{ children }</View>; - }, [ children, isFocused ] ); + return isScrollable || isNested ? ( + <View onLayout={ onLayout }>{ children }</View> + ) : ( + <ScrollView { ...listProps }> + <TouchableHighlight accessible={ false }> + <View onLayout={ onLayout }> + { children } + { ! isNested && ( + <View + style={ { + height: + safeAreaBottomInset || + styles.scrollableContent.paddingBottom, + } } + /> + ) } + </View> + </TouchableHighlight> + </ScrollView> + ); + }, [ children, isFocused, safeAreaBottomInset, listProps ] ); }; export default BottomSheetNavigationScreen; diff --git a/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/styles.native.scss b/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/styles.native.scss index d783d85ffd6714..ec15871537452a 100644 --- a/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/styles.native.scss +++ b/packages/components/src/mobile/bottom-sheet/bottom-sheet-navigation/styles.native.scss @@ -5,3 +5,7 @@ .backgroundDark { background-color: $modal-background-dark; } + +.scrollableContent { + padding-bottom: $grid-unit-20; +} diff --git a/packages/components/src/mobile/bottom-sheet/index.native.js b/packages/components/src/mobile/bottom-sheet/index.native.js index 0bdc06bbc1e800..c937aad9773e43 100644 --- a/packages/components/src/mobile/bottom-sheet/index.native.js +++ b/packages/components/src/mobile/bottom-sheet/index.native.js @@ -7,9 +7,9 @@ import { Platform, PanResponder, Dimensions, - ScrollView, Keyboard, StatusBar, + ScrollView, TouchableHighlight, } from 'react-native'; import Modal from 'react-native-modal'; @@ -284,9 +284,9 @@ class BottomSheet extends Component { contentStyle = {}, getStylesFromColorScheme, onDismiss, - isChildrenScrollable, children, withHeaderSeparator = false, + hasNavigation, ...rest } = this.props; const { @@ -343,8 +343,6 @@ class BottomSheet extends Component { styles.content, hideHeader && styles.emptyHeader, contentStyle, - isChildrenScrollable && this.getContentStyle(), - contentStyle, isFullScreen && { flexGrow: 1 }, ], style: listStyle, @@ -353,7 +351,7 @@ class BottomSheet extends Component { automaticallyAdjustContentInsets: false, }; - const WrapperView = isChildrenScrollable ? View : ScrollView; + const WrapperView = hasNavigation ? View : ScrollView; const getHeader = () => ( <> @@ -370,7 +368,6 @@ class BottomSheet extends Component { { withHeaderSeparator && <View style={ styles.separator } /> } </> ); - return ( <Modal isVisible={ isVisible } @@ -421,7 +418,7 @@ class BottomSheet extends Component { ) } { ! hideHeader && getHeader() } <WrapperView - { ...( isChildrenScrollable + { ...( hasNavigation ? { style: listProps.style } : listProps ) } > @@ -438,14 +435,25 @@ class BottomSheet extends Component { .onHandleHardwareButtonPress, listProps, setIsFullScreen: this.setIsFullScreen, + safeAreaBottomInset, } } > - <TouchableHighlight accessible={ false }> + { hasNavigation ? ( <>{ children }</> - </TouchableHighlight> + ) : ( + <TouchableHighlight accessible={ false }> + <>{ children }</> + </TouchableHighlight> + ) } </BottomSheetProvider> - { ! isChildrenScrollable && ( - <View style={ { height: safeAreaBottomInset } } /> + { ! hasNavigation && ( + <View + style={ { + height: + safeAreaBottomInset || + styles.scrollableContent.paddingBottom, + } } + /> ) } </WrapperView> </KeyboardAvoidingView> diff --git a/packages/components/src/mobile/bottom-sheet/link-cell.native.js b/packages/components/src/mobile/bottom-sheet/link-cell.native.js index c4ff2da87b76eb..4d3c2f61a7fd8f 100644 --- a/packages/components/src/mobile/bottom-sheet/link-cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/link-cell.native.js @@ -12,10 +12,10 @@ import styles from './styles.scss'; const { placeholderColor } = styles; -export default function LinkCell( { value, onPress } ) { +export default function LinkCell( { value, onPress, showIcon = true } ) { return ( <Cell - icon={ link } + icon={ showIcon && link } label={ __( 'Link to' ) } // since this is not actually editable, we treat value as a placeholder value={ value || __( 'Search or type URL' ) } diff --git a/packages/components/src/mobile/bottom-sheet/navigation-header.native.js b/packages/components/src/mobile/bottom-sheet/navigation-header.native.js index ff1aed85517fec..372ae79f54df96 100644 --- a/packages/components/src/mobile/bottom-sheet/navigation-header.native.js +++ b/packages/components/src/mobile/bottom-sheet/navigation-header.native.js @@ -7,7 +7,13 @@ import { View, TouchableWithoutFeedback, Text, Platform } from 'react-native'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { check, Icon, chevronLeft, arrowLeft, close } from '@wordpress/icons'; +import { + check, + Icon, + chevronBackIOS, + arrowLeft, + close, +} from '@wordpress/icons'; import { usePreferredColorSchemeStyle } from '@wordpress/compose'; /** @@ -51,8 +57,8 @@ function BottomSheetNavigationHeader( { if ( isIOS ) { backIcon = isFullscreen ? undefined : ( <Icon - icon={ chevronLeft } - size={ 40 } + icon={ chevronBackIOS } + size={ 21 } style={ chevronLeftStyle } /> ); diff --git a/packages/components/src/mobile/bottom-sheet/range-cell.native.js b/packages/components/src/mobile/bottom-sheet/range-cell.native.js index 6f082edb37ded6..b7a37c66b9b61c 100644 --- a/packages/components/src/mobile/bottom-sheet/range-cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/range-cell.native.js @@ -5,10 +5,7 @@ import { Platform, AccessibilityInfo, findNodeHandle, - TextInput, View, - PixelRatio, - AppState, } from 'react-native'; import Slider from '@react-native-community/slider'; @@ -23,107 +20,35 @@ import { withPreferredColorScheme } from '@wordpress/compose'; * Internal dependencies */ import Cell from './cell'; -import { toFixed, removeNonDigit } from '../utils'; import styles from './range-cell.scss'; -import borderStyles from './borderStyles.scss'; +import RangeTextInput from './range-text-input'; +import { toFixed } from '../utils'; const isIOS = Platform.OS === 'ios'; class BottomSheetRangeCell extends Component { constructor( props ) { super( props ); - this.onInputFocus = this.onInputFocus.bind( this ); - this.onInputBlur = this.onInputBlur.bind( this ); this.onChangeValue = this.onChangeValue.bind( this ); + this.onChange = this.onChange.bind( this ); this.onCellPress = this.onCellPress.bind( this ); - this.handleChangePixelRatio = this.handleChangePixelRatio.bind( this ); - this.onSubmitEditing = this.onSubmitEditing.bind( this ); - this.onChangeText = this.onChangeText.bind( this ); - const initialValue = this.validateInput( - props.value || props.defaultValue || props.minimumValue - ); - const fontScale = this.getFontScale(); + + const { value, defaultValue, minimumValue } = props; + const initialValue = Number( value || defaultValue || minimumValue ); this.state = { accessible: true, - hasFocus: false, - fontScale, inputValue: initialValue, sliderValue: initialValue, }; } - componentDidMount() { - AppState.addEventListener( 'change', this.handleChangePixelRatio ); - } - - componentWillUnmount() { - AppState.removeEventListener( 'change', this.handleChangePixelRatio ); - } - - getFontScale() { - return PixelRatio.getFontScale() < 1 ? 1 : PixelRatio.getFontScale(); - } - - handleChangePixelRatio( nextAppState ) { - if ( nextAppState === 'active' ) { - this.setState( { fontScale: this.getFontScale() } ); - } - } - - onInputFocus() { - this.setState( { - hasFocus: true, - } ); - } - - onInputBlur() { - this.onChangeText( '' + this.state.sliderValue ); - this.setState( { - hasFocus: false, - } ); - } - - validateInput( text ) { - const { minimumValue, maximumValue, decimalNum } = this.props; - if ( ! text ) { - return minimumValue; - } - if ( typeof text === 'number' ) { - return Math.min( Math.max( text, minimumValue ), maximumValue ); - } - return Math.min( - Math.max( removeNonDigit( text, decimalNum ), minimumValue ), - maximumValue - ); - } - - updateValue( value ) { - const { onChange } = this.props; - const validValue = this.validateInput( value ); - - this.announceCurrentValue( `${ validValue }` ); - onChange( validValue ); - } - onChangeValue( initialValue ) { - const { decimalNum } = this.props; + const { decimalNum, onChange } = this.props; initialValue = toFixed( initialValue, decimalNum ); this.setState( { inputValue: initialValue } ); - this.updateValue( initialValue ); - } - - onChangeText( textValue ) { - const { decimalNum } = this.props; - const inputValue = removeNonDigit( textValue, decimalNum ); - textValue = inputValue.replace( ',', '.' ); - textValue = toFixed( textValue, decimalNum ); - const value = this.validateInput( textValue ); - this.setState( { - inputValue, - sliderValue: value, - } ); - this.updateValue( value ); + this.announceCurrentValue( `${ initialValue }` ); + onChange( initialValue ); } onCellPress() { @@ -134,26 +59,20 @@ class BottomSheetRangeCell extends Component { } } - onSubmitEditing( { nativeEvent: { text } } ) { - const { decimalNum } = this.props; - if ( ! isNaN( Number( text ) ) ) { - text = toFixed( text.replace( ',', '.' ), decimalNum ); - const validValue = this.validateInput( text ); - - if ( this.state.inputValue !== validValue ) { - this.setState( { inputValue: validValue } ); - this.announceCurrentValue( `${ validValue }` ); - this.props.onChange( validValue ); - } - } - } - announceCurrentValue( value ) { /* translators: %s: current cell value. */ const announcement = sprintf( __( 'Current value is %s' ), value ); AccessibilityInfo.announceForAccessibility( announcement ); } + onChange( nextValue ) { + const { onChange } = this.props; + this.setState( { + sliderValue: nextValue, + } ); + onChange( nextValue ); + } + render() { const { value, @@ -168,20 +87,15 @@ class BottomSheetRangeCell extends Component { : '#5198d9', maximumTrackTintColor = isIOS ? '#e9eff3' : '#909090', thumbTintColor = ! isIOS && '#00669b', - getStylesFromColorScheme, - rangePreview, + preview, cellContainerStyle, shouldDisplayTextInput = true, + children, + decimalNum, ...cellProps } = this.props; - const { - hasFocus, - accessible, - fontScale, - inputValue, - sliderValue, - } = this.state; + const { accessible, inputValue, sliderValue } = this.state; const accessibilityLabel = sprintf( /* translators: accessibility text. Inform about current value. %1$s: Control label %2$s: Current value. */ @@ -193,11 +107,6 @@ class BottomSheetRangeCell extends Component { value ); - const defaultSliderStyle = getStylesFromColorScheme( - styles.sliderTextInput, - styles.sliderDarkTextInput - ); - const containerStyle = [ styles.container, isIOS ? styles.containerIOS : styles.containerAndroid, @@ -217,6 +126,7 @@ class BottomSheetRangeCell extends Component { activeOpacity={ 1 } accessible={ accessible } onPress={ this.onCellPress } + valueStyle={ styles.valueStyle } accessibilityLabel={ accessibilityLabel } accessibilityHint={ /* translators: accessibility text (hint for focusing a slider) */ @@ -224,9 +134,9 @@ class BottomSheetRangeCell extends Component { } > <View style={ containerStyle }> - { rangePreview } + { preview } <Slider - value={ this.validateInput( sliderValue ) } + value={ sliderValue } defaultValue={ defaultValue } disabled={ disabled } step={ step } @@ -245,22 +155,18 @@ class BottomSheetRangeCell extends Component { accessibilityRole={ 'adjustable' } /> { shouldDisplayTextInput && ( - <TextInput - style={ [ - defaultSliderStyle, - borderStyles.borderStyle, - hasFocus && borderStyles.isSelected, - { width: 40 * fontScale }, - ] } - onChangeText={ this.onChangeText } - onSubmitEditing={ this.onSubmitEditing } - onFocus={ this.onInputFocus } - onBlur={ this.onInputBlur } - keyboardType="numeric" - returnKeyType="done" + <RangeTextInput + label={ cellProps.label } + onChange={ this.onChange } defaultValue={ `${ inputValue }` } value={ inputValue } - /> + min={ minimumValue } + max={ maximumValue } + step={ step } + decimalNum={ decimalNum } + > + { children } + </RangeTextInput> ) } </View> </Cell> diff --git a/packages/components/src/mobile/bottom-sheet/range-cell.native.scss b/packages/components/src/mobile/bottom-sheet/range-cell.native.scss index ed7ecb16ab372b..0098c89631a0bd 100644 --- a/packages/components/src/mobile/bottom-sheet/range-cell.native.scss +++ b/packages/components/src/mobile/bottom-sheet/range-cell.native.scss @@ -1,26 +1,14 @@ .sliderIOS { flex-grow: 1; + min-height: $grid-unit-60; } .sliderAndroid { flex-grow: 1; + min-height: $grid-unit-60; margin-left: -6px; } -.sliderTextInput { - min-height: 25px; - align-self: center; - margin-left: 10px; - border-color: $light-gray-400; - padding-top: 0; - padding-bottom: 0; - text-align: center; -} - -.sliderDarkTextInput { - border-color: $gray-70; -} - .isSelected { border-width: 2px; border-color: $blue-wordpress; @@ -46,3 +34,7 @@ padding-top: 8px; padding-bottom: 8px; } + +.valueStyle { + height: 0; +} diff --git a/packages/components/src/mobile/bottom-sheet/range-text-input.native.js b/packages/components/src/mobile/bottom-sheet/range-text-input.native.js new file mode 100644 index 00000000000000..0be4ca8dbb5633 --- /dev/null +++ b/packages/components/src/mobile/bottom-sheet/range-text-input.native.js @@ -0,0 +1,244 @@ +/** + * External dependencies + */ +import { + AccessibilityInfo, + View, + TextInput, + PixelRatio, + AppState, + Platform, + Text, + TouchableWithoutFeedback, +} from 'react-native'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { withPreferredColorScheme } from '@wordpress/compose'; +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { toFixed, removeNonDigit } from '../utils'; +import styles from './styles.scss'; +import borderStyles from './borderStyles.scss'; + +const isIOS = Platform.OS === 'ios'; + +class RangeTextInput extends Component { + constructor( props ) { + super( props ); + + this.announceCurrentValue = this.announceCurrentValue.bind( this ); + this.onInputFocus = this.onInputFocus.bind( this ); + this.onInputBlur = this.onInputBlur.bind( this ); + this.handleChangePixelRatio = this.handleChangePixelRatio.bind( this ); + this.onSubmitEditing = this.onSubmitEditing.bind( this ); + this.onChangeText = this.onChangeText.bind( this ); + + const { value, defaultValue, min } = props; + const initialValue = value || defaultValue || min; + + const fontScale = this.getFontScale(); + + this.state = { + fontScale, + inputValue: initialValue, + controlValue: initialValue, + hasFocus: false, + }; + } + + componentDidMount() { + AppState.addEventListener( 'change', this.handleChangePixelRatio ); + } + + componentWillUnmount() { + AppState.removeEventListener( 'change', this.handleChangePixelRatio ); + clearTimeout( this.timeoutAnnounceValue ); + } + + componentDidUpdate( prevProps, prevState ) { + const { value } = this.props; + const { hasFocus, inputValue } = this.state; + + if ( prevProps.value !== value ) { + this.setState( { inputValue: value } ); + } + + if ( prevState.hasFocus !== hasFocus ) { + const validValue = this.validateInput( inputValue ); + this.setState( { inputValue: validValue } ); + } + + if ( ! prevState.hasFocus && hasFocus ) { + this._valueTextInput.focus(); + } + } + + getFontScale() { + return PixelRatio.getFontScale() < 1 ? 1 : PixelRatio.getFontScale(); + } + + handleChangePixelRatio( nextAppState ) { + if ( nextAppState === 'active' ) { + this.setState( { fontScale: this.getFontScale() } ); + } + } + + onInputFocus() { + this.setState( { + hasFocus: true, + } ); + } + + onInputBlur() { + const { inputValue } = this.state; + this.onChangeText( `${ inputValue }` ); + this.setState( { + hasFocus: false, + } ); + } + + validateInput( text ) { + const { min, max, decimalNum } = this.props; + let result = min; + if ( ! text ) { + return min; + } + + if ( typeof text === 'number' ) { + result = Math.max( text, min ); + return max ? Math.min( result, max ) : result; + } + + result = Math.max( removeNonDigit( text, decimalNum ), min ); + return max ? Math.min( result, max ) : result; + } + + updateValue( value ) { + const { onChange } = this.props; + const validValue = this.validateInput( value ); + + this.announceCurrentValue( `${ validValue }` ); + + onChange( validValue ); + } + + onChangeValue( initialValue ) { + const { decimalNum } = this.props; + initialValue = toFixed( initialValue, decimalNum ); + this.setState( { inputValue: initialValue } ); + this.updateValue( initialValue ); + } + + onChangeText( textValue ) { + const { decimalNum } = this.props; + const inputValue = removeNonDigit( textValue, decimalNum ); + + textValue = inputValue.replace( ',', '.' ); + textValue = toFixed( textValue, decimalNum ); + const value = this.validateInput( textValue ); + this.setState( { + inputValue, + controlValue: value, + } ); + this.updateValue( value ); + } + + onSubmitEditing( { nativeEvent: { text } } ) { + const { decimalNum } = this.props; + const { inputValue } = this.state; + + if ( ! isNaN( Number( text ) ) ) { + text = toFixed( text.replace( ',', '.' ), decimalNum ); + const validValue = this.validateInput( text ); + + if ( inputValue !== validValue ) { + this.setState( { inputValue: validValue } ); + this.announceCurrentValue( `${ validValue }` ); + this.props.onChange( validValue ); + } + } + } + + announceCurrentValue( value ) { + /* translators: %s: current cell value. */ + const announcement = sprintf( __( 'Current value is %s' ), value ); + AccessibilityInfo.announceForAccessibility( announcement ); + } + + render() { + const { getStylesFromColorScheme, children } = this.props; + const { fontScale, inputValue, hasFocus } = this.state; + + const textInputStyle = getStylesFromColorScheme( + styles.textInput, + styles.textInputDark + ); + + const verticalBorderStyle = getStylesFromColorScheme( + styles.verticalBorder, + styles.verticalBorderDark + ); + + const inputBorderStyles = [ + textInputStyle, + borderStyles.borderStyle, + hasFocus && borderStyles.isSelected, + ]; + + const valueFinalStyle = [ + ! isIOS ? inputBorderStyles : verticalBorderStyle, + { + width: 40 * fontScale, + }, + ]; + + return ( + <TouchableWithoutFeedback + onPress={ this.onInputFocus } + accessible={ false } + > + <View + style={ [ + styles.textInputContainer, + isIOS && inputBorderStyles, + ] } + accessible={ false } + > + { isIOS || hasFocus ? ( + <TextInput + ref={ ( c ) => ( this._valueTextInput = c ) } + style={ valueFinalStyle } + onChangeText={ this.onChangeText } + onSubmitEditing={ this.onSubmitEditing } + onFocus={ this.onInputFocus } + onBlur={ this.onInputBlur } + keyboardType="numeric" + returnKeyType="done" + numberOfLines={ 1 } + defaultValue={ `${ inputValue }` } + value={ inputValue } + pointerEvents={ hasFocus ? 'auto' : 'none' } + /> + ) : ( + <Text + style={ valueFinalStyle } + numberOfLines={ 1 } + ellipsizeMode="clip" + > + { inputValue } + </Text> + ) } + { children } + </View> + </TouchableWithoutFeedback> + ); + } +} + +export default withPreferredColorScheme( RangeTextInput ); diff --git a/packages/components/src/mobile/bottom-sheet/stepper-cell/index.native.js b/packages/components/src/mobile/bottom-sheet/stepper-cell/index.native.js index 8511a0e4ca8851..ff8db3be5d5fbc 100644 --- a/packages/components/src/mobile/bottom-sheet/stepper-cell/index.native.js +++ b/packages/components/src/mobile/bottom-sheet/stepper-cell/index.native.js @@ -8,6 +8,7 @@ import { AccessibilityInfo, View, Platform } from 'react-native'; */ import { __, sprintf } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; +import { withPreferredColorScheme } from '@wordpress/compose'; /** * Internal dependencies @@ -15,10 +16,14 @@ import { Component } from '@wordpress/element'; import Cell from '../cell'; import Stepper from './stepper'; import styles from './style.scss'; +import RangeTextInput from '../range-text-input'; +import { toFixed } from '../../utils'; const STEP_DELAY = 200; const DEFAULT_STEP = 1; +const isIOS = Platform.OS === 'ios'; + class BottomSheetStepperCell extends Component { constructor( props ) { super( props ); @@ -33,7 +38,15 @@ class BottomSheetStepperCell extends Component { this ); this.onPressOut = this.onPressOut.bind( this ); - this.startPressInterval = this.startPressInterval.bind( this ); + + const { value, defaultValue, min } = props; + + const initialValue = value || defaultValue || min; + + this.state = { + inputValue: initialValue, + stepperValue: initialValue, + }; } componentWillUnmount() { @@ -43,21 +56,27 @@ class BottomSheetStepperCell extends Component { } onIncrementValue() { - const { step, max, onChange, value } = this.props; - const newValue = value + step; + const { step, max, onChange, value, decimalNum } = this.props; + const newValue = toFixed( value + step, decimalNum ); - if ( newValue <= max ) { + if ( newValue <= max || max === undefined ) { onChange( newValue ); + this.setState( { + inputValue: newValue, + } ); this.announceValue( newValue ); } } onDecrementValue() { - const { step, min, onChange, value } = this.props; - const newValue = value - step; + const { step, min, onChange, value, decimalNum } = this.props; + const newValue = toFixed( value - step, decimalNum ); if ( newValue >= min ) { onChange( newValue ); + this.setState( { + inputValue: newValue, + } ); this.announceValue( newValue ); } } @@ -109,7 +128,21 @@ class BottomSheetStepperCell extends Component { } render() { - const { label, icon, min, max, value, separatorType } = this.props; + const { + label, + icon, + min, + max, + value, + separatorType, + children, + shouldDisplayTextInput = false, + preview, + onChange, + decimalNum, + cellContainerStyle, + } = this.props; + const { inputValue } = this.state; const isMinValue = value === min; const isMaxValue = value === max; const labelStyle = [ @@ -122,6 +155,10 @@ class BottomSheetStepperCell extends Component { label, value ); + const containerStyle = [ + styles.rowContainer, + isIOS ? styles.containerIOS : styles.containerAndroid, + ]; return ( <View @@ -146,8 +183,14 @@ class BottomSheetStepperCell extends Component { <Cell accessibilityRole="none" accessible={ false } - cellContainerStyle={ styles.cellContainerStyles } - cellRowContainerStyle={ styles.cellRowStyles } + cellContainerStyle={ [ + styles.cellContainerStyle, + preview && styles.columnContainer, + cellContainerStyle, + ] } + cellRowContainerStyle={ + preview ? containerStyle : styles.cellRowStyles + } disabled={ true } editable={ false } icon={ icon } @@ -156,14 +199,32 @@ class BottomSheetStepperCell extends Component { leftAlign={ true } separatorType={ separatorType } > - <Stepper - isMaxValue={ isMaxValue } - isMinValue={ isMinValue } - onPressInDecrement={ this.onDecrementValuePressIn } - onPressInIncrement={ this.onIncrementValuePressIn } - onPressOut={ this.onPressOut } - value={ value } - /> + <View style={ preview && containerStyle }> + { preview } + <Stepper + isMaxValue={ isMaxValue } + isMinValue={ isMinValue } + onPressInDecrement={ this.onDecrementValuePressIn } + onPressInIncrement={ this.onIncrementValuePressIn } + onPressOut={ this.onPressOut } + value={ value } + shouldDisplayTextInput={ shouldDisplayTextInput } + > + { shouldDisplayTextInput && ( + <RangeTextInput + label={ label } + onChange={ onChange } + defaultValue={ `${ inputValue }` } + value={ inputValue } + min={ min } + step={ 1 } + decimalNum={ decimalNum } + > + { children } + </RangeTextInput> + ) } + </Stepper> + </View> </Cell> </View> ); @@ -174,4 +235,4 @@ BottomSheetStepperCell.defaultProps = { step: DEFAULT_STEP, }; -export default BottomSheetStepperCell; +export default withPreferredColorScheme( BottomSheetStepperCell ); diff --git a/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.android.js b/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.android.js index ca7f90eb845df0..efa69f3892326f 100644 --- a/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.android.js +++ b/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.android.js @@ -22,6 +22,8 @@ function Stepper( { onPressInIncrement, onPressOut, value, + shouldDisplayTextInput, + children, } ) { const valueStyle = getStylesFromColorScheme( styles.value, @@ -33,11 +35,7 @@ function Stepper( { ); return ( - <View - style={ styles.container } - accesibility={ false } - importantForAccessibility="no-hide-descendants" - > + <View style={ styles.container }> <TouchableOpacity disabled={ isMinValue } onPressIn={ onPressInDecrement } @@ -49,11 +47,14 @@ function Stepper( { > <Icon icon={ chevronDown } - size={ 18 } + size={ 24 } color={ buttonIconStyle.color } /> </TouchableOpacity> - <Text style={ valueStyle }>{ value }</Text> + { ! shouldDisplayTextInput && ( + <Text style={ [ valueStyle, styles.spacings ] }>{ value }</Text> + ) } + { children } <TouchableOpacity disabled={ isMaxValue } onPressIn={ onPressInIncrement } @@ -65,7 +66,7 @@ function Stepper( { > <Icon icon={ chevronUp } - size={ 18 } + size={ 24 } color={ buttonIconStyle.color } /> </TouchableOpacity> diff --git a/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.ios.js b/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.ios.js index e226b8adb4b20f..df6fd8bfb7966a 100644 --- a/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.ios.js +++ b/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.ios.js @@ -22,6 +22,8 @@ function Stepper( { onPressInIncrement, onPressOut, value, + children, + shouldDisplayTextInput, } ) { const valueStyle = getStylesFromColorScheme( styles.value, @@ -34,7 +36,10 @@ function Stepper( { return ( <View style={ styles.container }> - <Text style={ valueStyle }>{ value }</Text> + { ! shouldDisplayTextInput && ( + <Text style={ valueStyle }>{ value }</Text> + ) } + { children } <TouchableOpacity disabled={ isMinValue } onPressIn={ onPressInDecrement } diff --git a/packages/components/src/mobile/bottom-sheet/stepper-cell/style.native.scss b/packages/components/src/mobile/bottom-sheet/stepper-cell/style.native.scss index 597bb22dc726e7..f84c9c23cec2eb 100644 --- a/packages/components/src/mobile/bottom-sheet/stepper-cell/style.native.scss +++ b/packages/components/src/mobile/bottom-sheet/stepper-cell/style.native.scss @@ -6,6 +6,26 @@ justify-content: flex-end; } +.rowContainer { + flex-direction: row; + align-items: center; +} + +.columnContainer { + margin-top: 13px; + flex-direction: column; +} + +.containerIOS { + padding-top: 6px; + padding-bottom: 6px; +} + +.containerAndroid { + padding-top: 8px; + padding-bottom: 8px; +} + .cellContainerStyles { flex-direction: row; align-items: flex-start; @@ -41,7 +61,6 @@ width: 32px; height: 32px; color: #87a6bc; - margin-left: 12px; align-items: center; justify-content: center; } @@ -58,7 +77,11 @@ .value { color: #2e4453; font-size: 16px; +} + +.spacings { margin-left: 12px; + margin-right: 12px; } .valueTextDark { diff --git a/packages/components/src/mobile/bottom-sheet/styles.native.scss b/packages/components/src/mobile/bottom-sheet/styles.native.scss index c9bfba6dac1dfe..4269ab14ec30f2 100644 --- a/packages/components/src/mobile/bottom-sheet/styles.native.scss +++ b/packages/components/src/mobile/bottom-sheet/styles.native.scss @@ -172,6 +172,37 @@ color: $blue-30; } +// Range Text Input + +.verticalBorder { + border-right-width: 1px; + text-align: center; + border-color: $light-gray-400; +} + +.verticalBorderDark { + border-color: $gray-70; +} + +.textInputContainer { + flex-direction: row; + margin-left: $grid-unit; +} + +.textInput { + min-height: 25px; + border-color: $light-gray-400; + padding-top: 4px; + padding-bottom: 4px; + text-align: center; + color: $gray-dark; +} + +.textInputDark { + border-color: $gray-70; + color: $white; +} + // Navigation Header .bottomSheetHeader { diff --git a/packages/components/src/mobile/global-styles-context/index.native.js b/packages/components/src/mobile/global-styles-context/index.native.js index 2ff310d27ee3e7..5e4263ab4ad969 100644 --- a/packages/components/src/mobile/global-styles-context/index.native.js +++ b/packages/components/src/mobile/global-styles-context/index.native.js @@ -1,10 +1,48 @@ +/** + * External dependencies + */ +import { pick } from 'lodash'; + /** * WordPress dependencies */ import { createContext, useContext } from '@wordpress/element'; +/** + * Internal dependencies + */ +import { + BLOCK_STYLE_ATTRIBUTES, + getBlockPaddings, + getBlockColors, +} from './utils'; + const GlobalStylesContext = createContext( { style: {} } ); +export const getMergedGlobalStyles = ( + globalStyle, + wrapperPropsStyle, + blockAttributes, + defaultColors +) => { + const blockStyleAttributes = pick( + blockAttributes, + BLOCK_STYLE_ATTRIBUTES + ); + const mergedStyle = { + ...globalStyle, + ...wrapperPropsStyle, + }; + const blockPaddings = getBlockPaddings( + mergedStyle, + wrapperPropsStyle, + blockStyleAttributes + ); + const blockColors = getBlockColors( blockStyleAttributes, defaultColors ); + + return { ...mergedStyle, ...blockPaddings, ...blockColors }; +}; + export const useGlobalStyles = () => { const globalStyles = useContext( GlobalStylesContext ); diff --git a/packages/components/src/mobile/global-styles-context/test/utils.native.js b/packages/components/src/mobile/global-styles-context/test/utils.native.js new file mode 100644 index 00000000000000..396dee85b215f6 --- /dev/null +++ b/packages/components/src/mobile/global-styles-context/test/utils.native.js @@ -0,0 +1,86 @@ +/** + * Internal dependencies + */ +import { getBlockPaddings, getBlockColors } from '../utils'; + +const DEFAULT_COLORS = [ + { color: '#cd2653', name: 'Accent Color', slug: 'accent' }, + { color: '#000000', name: 'Primary', slug: 'primary' }, + { color: '#6d6d6d', name: 'Secondary', slug: 'secondary' }, +]; + +describe( 'getBlockPaddings', () => { + const PADDING = 12; + + it( 'returns no paddings for a block without background color', () => { + const paddings = getBlockPaddings( + { color: 'red' }, + { backgroundColor: 'red' }, + { textColor: 'primary' } + ); + expect( paddings ).toEqual( expect.objectContaining( {} ) ); + } ); + + it( 'returns paddings for a block with background color', () => { + const paddings = getBlockPaddings( + { color: 'red' }, + {}, + { backgroundColor: 'red', textColor: 'primary' } + ); + expect( paddings ).toEqual( + expect.objectContaining( { padding: PADDING } ) + ); + } ); + + it( 'returns no paddings for an inner block without background color within a parent block with background color', () => { + const paddings = getBlockPaddings( + { backgroundColor: 'blue', color: 'yellow', padding: PADDING }, + {}, + { textColor: 'primary' } + ); + + expect( paddings ).toEqual( + expect.not.objectContaining( { padding: PADDING } ) + ); + } ); +} ); + +describe( 'getBlockColors', () => { + it( 'returns the theme colors correctly', () => { + const blockColors = getBlockColors( + { backgroundColor: 'accent', textColor: 'secondary' }, + DEFAULT_COLORS + ); + expect( blockColors ).toEqual( + expect.objectContaining( { + backgroundColor: '#cd2653', + color: '#6d6d6d', + } ) + ); + } ); + + it( 'returns custom background color correctly', () => { + const blockColors = getBlockColors( + { backgroundColor: '#222222', textColor: 'accent' }, + DEFAULT_COLORS + ); + expect( blockColors ).toEqual( + expect.objectContaining( { + backgroundColor: '#222222', + color: '#cd2653', + } ) + ); + } ); + + it( 'returns custom text color correctly', () => { + const blockColors = getBlockColors( + { textColor: '#4ddddd' }, + DEFAULT_COLORS + ); + expect( blockColors ).toEqual( + expect.objectContaining( { + color: '#4ddddd', + } ) + ); + } ); +} ); diff --git a/packages/components/src/mobile/global-styles-context/utils.native.js b/packages/components/src/mobile/global-styles-context/utils.native.js new file mode 100644 index 00000000000000..c8e2bce0c91f8a --- /dev/null +++ b/packages/components/src/mobile/global-styles-context/utils.native.js @@ -0,0 +1,68 @@ +/** + * External dependencies + */ +import { find, startsWith } from 'lodash'; + +export const BLOCK_STYLE_ATTRIBUTES = [ 'textColor', 'backgroundColor' ]; + +// Mapping style properties name to native +const BLOCK_STYLE_ATTRIBUTES_MAPPING = { + textColor: 'color', +}; + +const PADDING = 12; // solid-border-space + +export function getBlockPaddings( + mergedStyle, + wrapperPropsStyle, + blockStyleAttributes +) { + const blockPaddings = {}; + + if ( + ! mergedStyle.padding && + ( wrapperPropsStyle?.backgroundColor || + blockStyleAttributes?.backgroundColor ) + ) { + blockPaddings.padding = PADDING; + return blockPaddings; + } + + // Prevent adding extra paddings to inner blocks without background colors + if ( + mergedStyle?.padding && + ! wrapperPropsStyle?.backgroundColor && + ! blockStyleAttributes?.backgroundColor + ) { + blockPaddings.padding = undefined; + } + + return blockPaddings; +} + +export function getBlockColors( blockStyleAttributes, defaultColors ) { + const blockStyles = {}; + + Object.entries( blockStyleAttributes ).forEach( ( [ key, value ] ) => { + const isCustomColor = startsWith( value, '#' ); + let styleKey = key; + + if ( BLOCK_STYLE_ATTRIBUTES_MAPPING[ styleKey ] ) { + styleKey = BLOCK_STYLE_ATTRIBUTES_MAPPING[ styleKey ]; + } + + if ( ! isCustomColor ) { + const mappedColor = find( defaultColors, { + slug: value, + } ); + + if ( mappedColor ) { + blockStyles[ styleKey ] = mappedColor.color; + } + } else { + blockStyles[ styleKey ] = value; + } + } ); + + return blockStyles; +} diff --git a/packages/components/src/mobile/gradient/index.native.js b/packages/components/src/mobile/gradient/index.native.js index ed0e0b76664104..57ccd2aaf739b7 100644 --- a/packages/components/src/mobile/gradient/index.native.js +++ b/packages/components/src/mobile/gradient/index.native.js @@ -3,35 +3,98 @@ */ import { View, Platform } from 'react-native'; import RNLinearGradient from 'react-native-linear-gradient'; +import gradientParser from 'gradient-parser'; /** * WordPress dependencies */ import { colorsUtils } from '@wordpress/components'; import { RadialGradient, Stop, SVG, Defs, Rect } from '@wordpress/primitives'; import { useResizeObserver } from '@wordpress/compose'; +import { useMemo } from '@wordpress/element'; /** * Internal dependencies */ import styles from './style.scss'; -function getGradientAngle( gradientValue ) { - const matchDeg = /(\d+)deg/g; +export function getGradientAngle( gradientValue ) { + const angleBase = 45; + const matchAngle = /\(((\d+deg)|(to\s[^,]+))/; + const angle = matchAngle.exec( gradientValue ) + ? matchAngle.exec( gradientValue )[ 1 ] + : '180deg'; - return Number( matchDeg.exec( gradientValue )[ 1 ] ); + const angleType = angle.includes( 'deg' ) ? 'angle' : 'sideOrCorner'; + + if ( angleType === 'sideOrCorner' ) { + switch ( angle ) { + case 'to top': + return 0; + case 'to top right': + case 'to right top': + return angleBase; + case 'to right': + return 2 * angleBase; + case 'to bottom right': + case 'to right bottom': + return 3 * angleBase; + case 'to bottom': + return 4 * angleBase; + case 'to bottom left': + case 'to left bottom': + return 5 * angleBase; + case 'to left': + return 6 * angleBase; + case 'to top left': + case 'to left top': + return 7 * angleBase; + } + } else if ( angleType === 'angle' ) { + return parseFloat( angle ); + } else return 4 * angleBase; } -function getGradientColorGroup( gradientValue ) { - const matchColorGroup = /(rgba|rgb|#)(.+?)[\%]/g; +export function getGradientColorGroup( gradientValue ) { + const colorNeedParenthesis = [ 'rgb', 'rgba' ]; + + const excludeSideOrCorner = /linear-gradient\(to\s+([a-z\s]+,)/; + + // Parser has some difficulties with angle defined as a side or corner (e.g. `to left`) + // so it's going to be excluded in order to matching color groups + const modifiedGradientValue = gradientValue.replace( + excludeSideOrCorner, + 'linear-gradient(' + ); + + return [].concat( + ...gradientParser.parse( modifiedGradientValue )?.map( ( gradient ) => + gradient.colorStops?.map( ( color, index ) => { + const { type, value, length } = color; + const fallbackLength = `${ + 100 * ( index / ( gradient.colorStops.length - 1 ) ) + }%`; + const colorLength = length + ? `${ length.value }${ length.type }` + : fallbackLength; + + if ( colorNeedParenthesis.includes( type ) ) { + return [ `${ type }(${ value.join( ',' ) })`, colorLength ]; + } else if ( type === 'literal' ) { + return [ value, colorLength ]; + } + return [ `#${ value }`, colorLength ]; + } ) + ) + ); +} - return gradientValue - .match( matchColorGroup ) - .map( ( color ) => color.split( ' ' ) ); +export function getGradientBaseColors( colorGroup ) { + return colorGroup.map( ( color ) => color[ 0 ] ); } -function getGradientBaseColors( gradientValue ) { - return getGradientColorGroup( gradientValue ).map( - ( color ) => color[ 0 ] +export function getColorLocations( colorGroup ) { + return colorGroup.map( + ( location ) => Number( location[ 1 ].replace( '%', '' ) ) / 100 ); } @@ -46,6 +109,18 @@ function Gradient( { const { width = 0, height = 0 } = sizes || {}; const { isGradient, getGradientType, gradients } = colorsUtils; + const colorGroup = useMemo( () => getGradientColorGroup( gradientValue ), [ + gradientValue, + ] ); + + const locations = useMemo( () => getColorLocations( colorGroup ), [ + colorGroup, + ] ); + + const colors = useMemo( () => getGradientBaseColors( colorGroup ), [ + colorGroup, + ] ); + if ( ! gradientValue || ! isGradient( gradientValue ) ) { return null; } @@ -53,16 +128,10 @@ function Gradient( { const isLinearGradient = getGradientType( gradientValue ) === gradients.linear; - const colorGroup = getGradientColorGroup( gradientValue ); - - const locations = colorGroup.map( - ( location ) => Number( location[ 1 ].replace( '%', '' ) ) / 100 - ); - if ( isLinearGradient ) { return ( <RNLinearGradient - colors={ getGradientBaseColors( gradientValue ) } + colors={ colors } useAngle={ true } angle={ getGradientAngle( gradientValue ) } locations={ locations } diff --git a/packages/components/src/mobile/gradient/test/index.native.js b/packages/components/src/mobile/gradient/test/index.native.js new file mode 100644 index 00000000000000..c8b4d458b8c3b9 --- /dev/null +++ b/packages/components/src/mobile/gradient/test/index.native.js @@ -0,0 +1,116 @@ +/** + * Internal dependencies + */ +import { + getGradientAngle, + getGradientBaseColors, + getColorLocations, + getGradientColorGroup, +} from '../index.native'; + +describe( 'getGradientAngle', () => { + it( 'returns default angle (180) when not specified in gradient value', () => { + const gradientValue = 'linear-gradient(#e66465, #9198e5)'; + expect( getGradientAngle( gradientValue ) ).toBe( 180 ); + } ); + it( 'returns correct angle if specified in words in gradient value', () => { + const angleBase = 45; + const sidesOrCorners = { + 'to top': 0, + 'to top right': angleBase, + 'to right top': angleBase, + 'to right': 2 * angleBase, + 'to bottom right': 3 * angleBase, + 'to right bottom': 3 * angleBase, + 'to bottom': 4 * angleBase, + 'to bottom left': 5 * angleBase, + 'to left bottom': 5 * angleBase, + 'to left': 6 * angleBase, + 'to top left': 7 * angleBase, + 'to left top': 7 * angleBase, + }; + const gradientColors = Object.keys( sidesOrCorners ).map( + ( sideOrCorner ) => { + return { + gradientValue: `linear-gradient(${ sideOrCorner }, #e66465, #9198e5)`, + sideOrCorner, + }; + } + ); + + gradientColors.forEach( ( gradientColor ) => { + const { gradientValue, sideOrCorner } = gradientColor; + expect( getGradientAngle( gradientValue ) ).toBe( + sidesOrCorners[ sideOrCorner ] + ); + } ); + } ); + it( 'returns an angle specified in gradient value', () => { + const gradientValue = 'linear-gradient(155deg,#e66465, #9198e5)'; + expect( getGradientAngle( gradientValue ) ).toBe( 155 ); + } ); +} ); + +describe( 'getGradientBaseColors', () => { + it( 'returns array of colors (hex) from gradient value', () => { + const colorGroup = getGradientColorGroup( + 'linear-gradient(#e66465, #9198e5)' + ); + + expect( getGradientBaseColors( colorGroup ) ).toStrictEqual( [ + '#e66465', + '#9198e5', + ] ); + } ); + it( 'returns an array of colors (rgb/rgba) from gradient value', () => { + const colorGroup = getGradientColorGroup( + `linear-gradient(336deg, rgb(0,0,255), rgba(0,0,255,.8) 70.71%)` + ); + expect( getGradientBaseColors( colorGroup ) ).toStrictEqual( [ + 'rgb(0,0,255)', + 'rgba(0,0,255,.8)', + ] ); + } ); + + it( 'return an array of colors (literal) from gradient value', () => { + const colorGroup = getGradientColorGroup( + 'linear-gradient(45deg, blue, red)' + ); + expect( getGradientBaseColors( colorGroup ) ).toStrictEqual( [ + 'blue', + 'red', + ] ); + } ); + + it( 'return an array of colors (mixed) from gradient value', () => { + const colorGroup = getGradientColorGroup( + 'linear-gradient(45deg, blue, #e66465, rgb(0,0,255), rgba(0,0,255,.8))' + ); + expect( getGradientBaseColors( colorGroup ) ).toStrictEqual( [ + 'blue', + '#e66465', + 'rgb(0,0,255)', + 'rgba(0,0,255,.8)', + ] ); + } ); +} ); + +describe( 'getColorLocations', () => { + it( 'returns an array of color locations specified in gradient value', () => { + const colorGroup = getGradientColorGroup( + 'linear-gradient(45deg, red 0%, blue 10%)' + ); + expect( getColorLocations( colorGroup ) ).toStrictEqual( [ 0, 0.1 ] ); + } ); + + it( 'returns an array of color locations adjusted proportionally when not specified in gradient value', () => { + const colorGroup = getGradientColorGroup( + 'linear-gradient(45deg, red, blue, green)' + ); + expect( getColorLocations( colorGroup ) ).toStrictEqual( [ + 0, + 0.5, + 1, + ] ); + } ); +} ); diff --git a/packages/components/src/mobile/link-picker/index.native.js b/packages/components/src/mobile/link-picker/index.native.js index 7bf2aa2a26ae2c..1374cd0483cbd0 100644 --- a/packages/components/src/mobile/link-picker/index.native.js +++ b/packages/components/src/mobile/link-picker/index.native.js @@ -2,7 +2,7 @@ * External dependencies */ import { useState } from 'react'; -import { SafeAreaView, TouchableOpacity } from 'react-native'; +import { SafeAreaView, TouchableOpacity, View } from 'react-native'; import { lowerCase, startsWith } from 'lodash'; /** @@ -81,48 +81,50 @@ export const LinkPicker = ( { ); return ( - <SafeAreaView style={ { height: '100%' } }> + <SafeAreaView style={ styles.safeArea }> <NavigationHeader screen={ __( 'Link to' ) } leftButtonOnPress={ cancel } applyButtonOnPress={ onSubmit } isFullscreen /> - <BottomSheet.Cell - icon={ link } - style={ omniCellStyle } - valueStyle={ styles.omniInput } - value={ value } - placeholder={ __( 'Search or type URL' ) } - autoCapitalize="none" - autoCorrect={ false } - keyboardType="url" - onChangeValue={ setValue } - onSubmit={ onSubmit } - /* eslint-disable-next-line jsx-a11y/no-autofocus */ - autoFocus={ true } - separatorType="none" - > - { value !== '' && ( - <TouchableOpacity - onPress={ clear } - style={ styles.clearIcon } - > - <Icon - icon={ cancelCircleFilled } - fill={ iconStyle.color } - size={ 24 } - /> - </TouchableOpacity> + <View style={ styles.contentContainer }> + <BottomSheet.Cell + icon={ link } + style={ omniCellStyle } + valueStyle={ styles.omniInput } + value={ value } + placeholder={ __( 'Search or type URL' ) } + autoCapitalize="none" + autoCorrect={ false } + keyboardType="url" + onChangeValue={ setValue } + onSubmit={ onSubmit } + /* eslint-disable-next-line jsx-a11y/no-autofocus */ + autoFocus + separatorType="none" + > + { value !== '' && ( + <TouchableOpacity + onPress={ clear } + style={ styles.clearIcon } + > + <Icon + icon={ cancelCircleFilled } + fill={ iconStyle.color } + size={ 24 } + /> + </TouchableOpacity> + ) } + </BottomSheet.Cell> + { !! value && ( + <LinkPickerResults + query={ value } + onLinkPicked={ pickLink } + directEntry={ directEntry } + /> ) } - </BottomSheet.Cell> - { !! value && ( - <LinkPickerResults - query={ value } - onLinkPicked={ pickLink } - directEntry={ directEntry } - /> - ) } + </View> </SafeAreaView> ); }; diff --git a/packages/components/src/mobile/link-picker/link-picker-results.native.js b/packages/components/src/mobile/link-picker/link-picker-results.native.js index ea25f0fb6778a2..83f1ccd92e7876 100644 --- a/packages/components/src/mobile/link-picker/link-picker-results.native.js +++ b/packages/components/src/mobile/link-picker/link-picker-results.native.js @@ -116,7 +116,7 @@ export default function LinkPickerResults( { { ...listProps } contentContainerStyle={ [ ...listProps.contentContainerStyle, - { paddingBottom: 0 }, + styles.list, ] } /> ) } diff --git a/packages/components/src/mobile/link-picker/link-picker-screen.native.js b/packages/components/src/mobile/link-picker/link-picker-screen.native.js new file mode 100644 index 00000000000000..3301779646c016 --- /dev/null +++ b/packages/components/src/mobile/link-picker/link-picker-screen.native.js @@ -0,0 +1,43 @@ +/** + * External dependencies + */ +import React from 'react'; +import { useNavigation, useRoute } from '@react-navigation/native'; +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { LinkPicker } from './'; + +const LinkPickerScreen = ( { returnScreenName } ) => { + const navigation = useNavigation(); + const route = useRoute(); + + const onLinkPicked = ( { url, title } ) => { + navigation.navigate( returnScreenName, { + inputValue: url, + text: title, + } ); + }; + + const onCancel = () => { + navigation.goBack(); + }; + + const { inputValue } = route.params; + return useMemo( () => { + return ( + <LinkPicker + value={ inputValue } + onLinkPicked={ onLinkPicked } + onCancel={ onCancel } + /> + ); + }, [ inputValue ] ); +}; + +export default LinkPickerScreen; diff --git a/packages/components/src/mobile/link-picker/styles.native.scss b/packages/components/src/mobile/link-picker/styles.native.scss index 5ff8542f229148..867c2d8eb6ceda 100644 --- a/packages/components/src/mobile/link-picker/styles.native.scss +++ b/packages/components/src/mobile/link-picker/styles.native.scss @@ -1,8 +1,6 @@ .omniCell { border-bottom-width: 1px; border-bottom-color: $light-gray-400; - padding-left: 16px; - padding-right: 16px; } .omniCellDark { @@ -29,3 +27,19 @@ .iconDark { color: $dark-tertiary; } + +.contentContainer { + padding-left: $grid-unit-20; + padding-right: $grid-unit-20; + flex: 1; +} + +.safeArea { + height: 100%; +} + +.list { + padding-left: 0; + padding-right: 0; + padding-bottom: $grid-unit-20; +} diff --git a/packages/components/src/mobile/link-settings/index.native.js b/packages/components/src/mobile/link-settings/index.native.js index b809080b4a29fa..ddd7b9e4ce58ad 100644 --- a/packages/components/src/mobile/link-settings/index.native.js +++ b/packages/components/src/mobile/link-settings/index.native.js @@ -8,16 +8,14 @@ import { Platform, Clipboard } from 'react-native'; import { compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; import { isURL, prependHTTP } from '@wordpress/url'; -import { useEffect, useState, useRef } from '@wordpress/element'; +import { useEffect, useState, useRef, useContext } from '@wordpress/element'; import { link, external } from '@wordpress/icons'; -/** - * Internal dependencies - */ /** * Internal dependencies */ import BottomSheet from '../bottom-sheet'; +import { BottomSheetContext } from '../bottom-sheet/bottom-sheet-context'; import PanelBody from '../../panel/body'; import TextControl from '../../text-control'; import ToggleControl from '../../toggle-control'; @@ -80,6 +78,8 @@ function LinkSettings( { editorSidebarOpened, // Specifies whether icon should be displayed next to the label showIcon, + onLinkCellPressed, + urlValue, } ) { const { url, label, linkTarget, rel } = attributes; const [ urlInputValue, setUrlInputValue ] = useState( '' ); @@ -87,6 +87,13 @@ function LinkSettings( { const [ linkRelInputValue, setLinkRelInputValue ] = useState( '' ); const prevEditorSidebarOpenedRef = useRef(); + const { onHandleClosingBottomSheet } = useContext( BottomSheetContext ); + useEffect( () => { + if ( ! onLinkCellPressed ) { + onHandleClosingBottomSheet( onCloseSettingsSheet ); + } + }, [ urlInputValue, labelInputValue, linkRelInputValue ] ); + useEffect( () => { prevEditorSidebarOpenedRef.current = editorSidebarOpened; } ); @@ -115,6 +122,15 @@ function LinkSettings( { } }, [ editorSidebarOpened, isVisible ] ); + useEffect( () => { + if ( ! urlValue && onEmptyURL ) { + onEmptyURL(); + } + setAttributes( { + url: prependHTTP( urlValue ), + } ); + }, [ urlValue ] ); + function onChangeURL( value ) { if ( ! value && onEmptyURL ) { onEmptyURL(); @@ -142,10 +158,10 @@ function LinkSettings( { function onChangeOpenInNewTab( value ) { const newLinkTarget = value ? '_blank' : undefined; - let updatedRel = rel; - if ( newLinkTarget && ! rel ) { + let updatedRel = linkRelInputValue; + if ( newLinkTarget && ! linkRelInputValue ) { updatedRel = NEW_TAB_REL; - } else if ( ! newLinkTarget && rel === NEW_TAB_REL ) { + } else if ( ! newLinkTarget && linkRelInputValue === NEW_TAB_REL ) { updatedRel = undefined; } @@ -176,23 +192,30 @@ function LinkSettings( { function getSettings() { return ( <> - { options.url && ( - <TextControl - icon={ showIcon && link } - label={ options.url.label } - value={ urlInputValue } - valuePlaceholder={ options.url.placeholder } - onChange={ onChangeURL } - onSubmit={ onCloseSettingsSheet } - autoCapitalize="none" - autoCorrect={ false } - // eslint-disable-next-line jsx-a11y/no-autofocus - autoFocus={ - Platform.OS === 'ios' && options.url.autoFocus - } - keyboardType="url" - /> - ) } + { options.url && + ( onLinkCellPressed ? ( + <BottomSheet.LinkCell + showIcon={ showIcon } + value={ url } + onPress={ onLinkCellPressed } + /> + ) : ( + <TextControl + icon={ showIcon && link } + label={ options.url.label } + value={ urlInputValue } + valuePlaceholder={ options.url.placeholder } + onChange={ onChangeURL } + onSubmit={ onCloseSettingsSheet } + autoCapitalize="none" + autoCorrect={ false } + // eslint-disable-next-line jsx-a11y/no-autofocus + autoFocus={ + Platform.OS === 'ios' && options.url.autoFocus + } + keyboardType="url" + /> + ) ) } { options.linkLabel && ( <TextControl label={ options.linkLabel.label } @@ -231,11 +254,7 @@ function LinkSettings( { } return ( - <BottomSheet - isVisible={ isVisible } - onClose={ onCloseSettingsSheet } - hideHeader - > + <> <PanelBody style={ styles.linkSettingsPanel }> { getSettings() } </PanelBody> @@ -248,7 +267,7 @@ function LinkSettings( { </PanelBody> ) } { actions && <PanelActions actions={ actions } /> } - </BottomSheet> + </> ); } diff --git a/packages/components/src/mobile/link-settings/link-settings-navigation.native.js b/packages/components/src/mobile/link-settings/link-settings-navigation.native.js new file mode 100644 index 00000000000000..1ca459f5cb5023 --- /dev/null +++ b/packages/components/src/mobile/link-settings/link-settings-navigation.native.js @@ -0,0 +1,44 @@ +/** + * Internal dependencies + */ +import BottomSheet from '../bottom-sheet'; +import LinkSettingsScreen from './link-settings-screen'; +import LinkPickerScreen from '../link-picker/link-picker-screen'; + +const linkSettingsScreens = { + settings: 'LinkSettingsScreen', + linkPicker: 'linkPicker', +}; + +function LinkSettingsNavigation( props ) { + if ( ! props.withBottomSheet ) { + return <LinkSettingsScreen { ...props } />; + } + return ( + <BottomSheet + isVisible={ props.isVisible } + onClose={ props.onClose } + hideHeader + hasNavigation + > + <BottomSheet.NavigationContainer animate main> + <BottomSheet.NavigationScreen + name={ linkSettingsScreens.settings } + > + <LinkSettingsScreen { ...props } withBottomSheet /> + </BottomSheet.NavigationScreen> + <BottomSheet.NavigationScreen + name={ linkSettingsScreens.linkPicker } + isScrollable + fullScreen + > + <LinkPickerScreen + returnScreenName={ linkSettingsScreens.settings } + /> + </BottomSheet.NavigationScreen> + </BottomSheet.NavigationContainer> + </BottomSheet> + ); +} + +export default LinkSettingsNavigation; diff --git a/packages/components/src/mobile/link-settings/link-settings-screen.native.js b/packages/components/src/mobile/link-settings/link-settings-screen.native.js new file mode 100644 index 00000000000000..c66892c9926c40 --- /dev/null +++ b/packages/components/src/mobile/link-settings/link-settings-screen.native.js @@ -0,0 +1,38 @@ +/** + * External dependencies + */ +import React from 'react'; +import { useNavigation, useRoute } from '@react-navigation/native'; +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; +/** + * Internal dependencies + */ +import LinkSettings from './'; + +const LinkSettingsScreen = ( props ) => { + const navigation = useNavigation(); + const route = useRoute(); + const { url = '' } = props.attributes || {}; + const { inputValue = url } = route.params || {}; + + const onLinkCellPressed = () => { + navigation.navigate( 'linkPicker', { inputValue } ); + }; + + return useMemo( () => { + return ( + <LinkSettings + onLinkCellPressed={ + props.hasPicker ? onLinkCellPressed : undefined + } + urlValue={ inputValue } + { ...props } + /> + ); + }, [ props, inputValue, navigation, route ] ); +}; + +export default LinkSettingsScreen; diff --git a/packages/components/src/mobile/picker/index.android.js b/packages/components/src/mobile/picker/index.android.js index 5a6bbfbf08ef2b..2f727b2b92a9b3 100644 --- a/packages/components/src/mobile/picker/index.android.js +++ b/packages/components/src/mobile/picker/index.android.js @@ -64,14 +64,13 @@ export default class Picker extends Component { hideHeader > <PanelBody title={ title } style={ styles.panelBody }> - { options.map( ( option, index ) => ( - <> + { options.map( ( option ) => ( + <View key={ `${ option.label }-${ option.value }` }> { options.length > 1 && option.separated && ( <Separator /> ) } <BottomSheet.Cell icon={ option.icon } - key={ index } leftAlign={ leftAlign } label={ option.label } separatorType={ 'none' } @@ -81,7 +80,7 @@ export default class Picker extends Component { disabled={ option.disabled } style={ option.disabled && styles.disabled } /> - </> + </View> ) ) } { ! hideCancelButton && ( <BottomSheet.Cell diff --git a/packages/components/src/mobile/picker/index.ios.js b/packages/components/src/mobile/picker/index.ios.js index 574123fa4d710c..b813c5803e8ecf 100644 --- a/packages/components/src/mobile/picker/index.ios.js +++ b/packages/components/src/mobile/picker/index.ios.js @@ -17,7 +17,7 @@ class Picker extends Component { title, destructiveButtonIndex, disabledButtonIndices, - anchor, + getAnchor, } = this.props; const labels = options.map( ( { label } ) => label ); const fullOptions = [ __( 'Cancel' ) ].concat( labels ); @@ -29,7 +29,7 @@ class Picker extends Component { cancelButtonIndex: 0, destructiveButtonIndex, disabledButtonIndices, - anchor, + anchor: getAnchor && getAnchor(), }, ( buttonIndex ) => { if ( buttonIndex === 0 ) { diff --git a/packages/components/src/mobile/utils/alignments.native.js b/packages/components/src/mobile/utils/alignments.native.js index c5f2712aae6d70..03fdcbd93f5e86 100644 --- a/packages/components/src/mobile/utils/alignments.native.js +++ b/packages/components/src/mobile/utils/alignments.native.js @@ -3,7 +3,7 @@ export const WIDE_ALIGNMENTS = { wide: 'wide', full: 'full', }, - excludeBlocks: [ 'core/columns' ], + excludeBlocks: [ 'core/columns', 'core/heading' ], }; export const ALIGNMENT_BREAKPOINTS = { diff --git a/packages/components/src/mobile/utils/use-unit-converter-to-mobile.native.js b/packages/components/src/mobile/utils/use-unit-converter-to-mobile.native.js new file mode 100644 index 00000000000000..ec34c1b7880217 --- /dev/null +++ b/packages/components/src/mobile/utils/use-unit-converter-to-mobile.native.js @@ -0,0 +1,82 @@ +/** + * External dependencies + */ +import { Dimensions } from 'react-native'; + +/** + * WordPress dependencies + */ +import { + useContext, + useEffect, + useState, + useMemo, + useCallback, +} from '@wordpress/element'; + +/** + * Internal dependencies + */ +import GlobalStylesContext from '../global-styles-context'; + +const getValueAndUnit = ( value, unit ) => { + const regex = /(\d+\.?\d*)(.*)/; + + const splitValue = `${ value }` + ?.match( regex ) + ?.filter( ( v ) => v !== '' ); + + if ( splitValue ) { + return { + valueToConvert: splitValue[ 1 ], + valueUnit: unit || splitValue[ 2 ], + }; + } + return undefined; +}; + +const useConvertUnitToMobile = ( value, unit ) => { + const [ windowSizes, setWindowSizes ] = useState( + Dimensions.get( 'window' ) + ); + + useEffect( () => { + Dimensions.addEventListener( 'change', onDimensionsChange ); + + return () => { + Dimensions.removeEventListener( 'change', onDimensionsChange ); + }; + }, [] ); + const { globalStyles: styles } = useContext( GlobalStylesContext ); + + const onDimensionsChange = useCallback( ( { window } ) => { + setWindowSizes( window ); + }, [] ); + + return useMemo( () => { + const { width, height } = windowSizes; + const { fontSize = 16 } = styles || {}; + + const { valueToConvert, valueUnit } = getValueAndUnit( value, unit ); + + switch ( valueUnit ) { + case 'rem': + case 'em': + return valueToConvert * fontSize; + case '%': + return Number( valueToConvert / 100 ) * width; + case 'px': + return Number( valueToConvert ); + case 'vw': + const vw = width / 100; + return Math.round( valueToConvert * vw ); + case 'vh': + const vh = height / 100; + return Math.round( valueToConvert * vh ); + default: + return Number( valueToConvert / 100 ) * width; + } + }, [ windowSizes, value, unit ] ); +}; + +export { useConvertUnitToMobile, getValueAndUnit }; diff --git a/packages/components/src/modal/frame.js b/packages/components/src/modal/frame.js index 25938225598a3b..8047832a6ec444 100644 --- a/packages/components/src/modal/frame.js +++ b/packages/components/src/modal/frame.js @@ -2,87 +2,95 @@ * External dependencies */ import classnames from 'classnames'; +import mergeRefs from 'react-merge-refs'; /** * WordPress dependencies */ -import { Component, createRef } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { ESCAPE } from '@wordpress/keycodes'; -import { compose } from '@wordpress/compose'; +import { + useFocusReturn, + useFocusOnMount, + useConstrainedTabbing, +} from '@wordpress/compose'; /** * Internal dependencies */ -import IsolatedEventContainer from '../isolated-event-container'; import withFocusOutside from '../higher-order/with-focus-outside'; -import withFocusReturn from '../higher-order/with-focus-return'; -import withConstrainedTabbing from '../higher-order/with-constrained-tabbing'; + +function ModalFrameContent( { + overlayClassName, + contentLabel, + aria: { describedby, labelledby }, + children, + className, + role, + style, + focusOnMount, + shouldCloseOnEsc, + onRequestClose, +} ) { + function handleEscapeKeyDown( event ) { + if ( shouldCloseOnEsc && event.keyCode === ESCAPE ) { + event.stopPropagation(); + if ( onRequestClose ) { + onRequestClose( event ); + } + } + } + const focusOnMountRef = useFocusOnMount( focusOnMount ); + const constrainedTabbingRef = useConstrainedTabbing(); + const focusReturnRef = useFocusReturn(); + + return ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions + <div + className={ classnames( + 'components-modal__screen-overlay', + overlayClassName + ) } + onKeyDown={ handleEscapeKeyDown } + > + <div + className={ classnames( 'components-modal__frame', className ) } + style={ style } + ref={ mergeRefs( [ + constrainedTabbingRef, + focusReturnRef, + focusOnMountRef, + ] ) } + role={ role } + aria-label={ contentLabel } + aria-labelledby={ contentLabel ? null : labelledby } + aria-describedby={ describedby } + tabIndex="-1" + > + { children } + </div> + </div> + ); +} class ModalFrame extends Component { constructor() { super( ...arguments ); - - this.containerRef = createRef(); - this.handleKeyDown = this.handleKeyDown.bind( this ); this.handleFocusOutside = this.handleFocusOutside.bind( this ); } - /** - * Focuses the first tabbable element when props.focusOnMount is true. - */ - componentDidMount() { - // Focus on mount - if ( this.props.focusOnMount ) { - this.containerRef.current.focus(); - } - } - /** * Callback function called when clicked outside the modal. * * @param {Object} event Mouse click event. */ handleFocusOutside( event ) { - if ( this.props.shouldCloseOnClickOutside ) { - this.onRequestClose( event ); - } - } - - /** - * Callback function called when a key is pressed. - * - * @param {KeyboardEvent} event Key down event. - */ - handleKeyDown( event ) { - if ( event.keyCode === ESCAPE ) { - this.handleEscapeKeyDown( event ); - } - } - - /** - * Handles a escape key down event. - * - * Calls onRequestClose and prevents propagation of the event outside the modal. - * - * @param {Object} event Key down event. - */ - handleEscapeKeyDown( event ) { - if ( this.props.shouldCloseOnEsc ) { - event.stopPropagation(); - this.onRequestClose( event ); - } - } - - /** - * Calls the onRequestClose callback props when it is available. - * - * @param {Object} event Event object. - */ - onRequestClose( event ) { - const { onRequestClose } = this.props; - if ( onRequestClose ) { - onRequestClose( event ); + if ( + this.props.shouldCloseOnClickOutside && + this.props.onRequestClose + ) { + this.props.onRequestClose( event ); } } @@ -92,46 +100,8 @@ class ModalFrame extends Component { * @return {WPElement} The modal frame element. */ render() { - const { - overlayClassName, - contentLabel, - aria: { describedby, labelledby }, - children, - className, - role, - style, - } = this.props; - - return ( - <IsolatedEventContainer - className={ classnames( - 'components-modal__screen-overlay', - overlayClassName - ) } - onKeyDown={ this.handleKeyDown } - > - <div - className={ classnames( - 'components-modal__frame', - className - ) } - style={ style } - ref={ this.containerRef } - role={ role } - aria-label={ contentLabel } - aria-labelledby={ contentLabel ? null : labelledby } - aria-describedby={ describedby } - tabIndex="-1" - > - { children } - </div> - </IsolatedEventContainer> - ); + return <ModalFrameContent { ...this.props } />; } } -export default compose( [ - withFocusReturn, - withConstrainedTabbing, - withFocusOutside, -] )( ModalFrame ); +export default withFocusOutside( ModalFrame ); diff --git a/packages/components/src/modal/style.scss b/packages/components/src/modal/style.scss index eecdbbbd4558a9..450cb553cf48c5 100644 --- a/packages/components/src/modal/style.scss +++ b/packages/components/src/modal/style.scss @@ -5,7 +5,7 @@ right: 0; bottom: 0; left: 0; - background-color: rgba($black, 0.7); + background-color: rgba($black, 0.35); z-index: z-index(".components-modal__screen-overlay"); // This animates the appearance of the white background. @@ -36,7 +36,7 @@ left: 50%; min-width: $modal-min-width; max-width: calc(100% - #{ $grid-unit-20 } - #{ $grid-unit-20 }); - max-height: calc(100% - #{ $header-height } - #{ $header-height }); + max-height: 90%; transform: translate(-50%, -50%); // Animate the modal frame/contents appearing on the page. diff --git a/packages/components/src/navigation/README.md b/packages/components/src/navigation/README.md index 853881d9360fcf..eeee3167382bdd 100644 --- a/packages/components/src/navigation/README.md +++ b/packages/components/src/navigation/README.md @@ -101,6 +101,13 @@ A callback to handle clicking on the back button. If this prop is provided then Optional className for the `NavigationMenu` component. +### hasSearch + +- Type: `boolean` +- Required: No + +Enable the search feature on the menu title. + ### `menu` - Type: `string` @@ -109,6 +116,13 @@ Optional className for the `NavigationMenu` component. The unique identifier of the menu. The root menu can omit this, and it will default to "root"; all other menus need to specify it. +### onSearch + +- Type: `function` +- Required: No + +When `hasSearch` is active, this function handles the search input's `onChange` event, making it controlled from the outside. It requires setting the `search` prop as well. + ### `parentMenu` - Type: `string` @@ -116,12 +130,26 @@ The unique identifier of the menu. The root menu can omit this, and it will defa The parent menu slug; used by nested menus to indicate their parent menu. +### search + +- Type: `string` +- Required: No + +When `hasSearch` is active and `onSearch` is provided, this controls the value of the search input. Required when the `onSearch` prop is provided. + +### `isEmpty` + +- Type: `boolean` +- Required: No + +Indicates whether the menu is empty or not. Used together with the `hideIfTargetMenuEmpty` prop of Navigation Item. + ### `title` - Type: `string` - Required: No -The menu title. +The menu title. It's also the field used by the menu search function. ## Navigation Group Props @@ -169,7 +197,7 @@ If provided, renders `a` instead of `button`. ### `item` - Type: `string` -- Required: Yes +- Required: No The unique identifier of the item. @@ -180,6 +208,13 @@ The unique identifier of the item. The child menu slug. If provided, clicking on the item will navigate to the target menu. +### `hideIfTargetMenuEmpty` + +- Type: `boolean` +- Required: No + +Indicates whether this item should be hidden if the menu specified in `navigateToMenu` is marked as empty in the `isEmpty` prop. Used together with the `isEmpty` prop of Navigation Menu. + ### `onClick` - Type: `function` @@ -187,6 +222,14 @@ The child menu slug. If provided, clicking on the item will navigate to the targ A callback to handle clicking on a menu item. +### `isText` + +- Type: `boolean` +- Required: No +- Default: false + +If set to true then the menu item will only act as a text-only item rather than a button. + ### `title` - Type: `string` diff --git a/packages/components/src/navigation/back-button/index.js b/packages/components/src/navigation/back-button/index.js index d218a6149b4bf4..9a5d750681ff3f 100644 --- a/packages/components/src/navigation/back-button/index.js +++ b/packages/components/src/navigation/back-button/index.js @@ -2,19 +2,19 @@ * External dependencies */ import classnames from 'classnames'; - /** * WordPress dependencies */ import { forwardRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Icon, chevronLeft } from '@wordpress/icons'; +import { Icon, chevronLeft, chevronRight } from '@wordpress/icons'; /** * Internal dependencies */ import { useNavigationContext } from '../context'; import { MenuBackButtonUI } from '../styles/navigation-styles'; +import { getRTL } from '../../utils/rtl'; function NavigationBackButton( { backButtonLabel, className, href, onClick, parentMenu }, @@ -34,11 +34,12 @@ function NavigationBackButton( onClick( event ); } + const animationDirection = getRTL() ? 'left' : 'right'; if ( parentMenu && ! event.defaultPrevented ) { - setActiveMenu( parentMenu, 'right' ); + setActiveMenu( parentMenu, animationDirection ); } }; - + const icon = getRTL() ? chevronRight : chevronLeft; return ( <MenuBackButtonUI className={ classes } @@ -47,10 +48,9 @@ function NavigationBackButton( ref={ ref } onClick={ handleOnClick } > - <Icon icon={ chevronLeft } /> + <Icon icon={ icon } /> { backButtonLabel || parentMenuTitle || __( 'Back' ) } </MenuBackButtonUI> ); } - export default forwardRef( NavigationBackButton ); diff --git a/packages/components/src/navigation/constants.js b/packages/components/src/navigation/constants.js index 753e5f51707922..b3b619e11a1591 100644 --- a/packages/components/src/navigation/constants.js +++ b/packages/components/src/navigation/constants.js @@ -1 +1,2 @@ export const ROOT_MENU = 'root'; +export const SEARCH_FOCUS_DELAY = 100; diff --git a/packages/components/src/navigation/context.js b/packages/components/src/navigation/context.js index c283c3899a89fe..5f47fd9b48d812 100644 --- a/packages/components/src/navigation/context.js +++ b/packages/components/src/navigation/context.js @@ -17,6 +17,7 @@ export const NavigationContext = createContext( { activeItem: undefined, activeMenu: ROOT_MENU, setActiveMenu: noop, + isMenuEmpty: noop, navigationTree: { items: {}, @@ -28,6 +29,9 @@ export const NavigationContext = createContext( { getMenu: noop, addMenu: noop, removeMenu: noop, + childMenu: {}, + traverseMenu: noop, + isMenuEmpty: noop, }, } ); export const useNavigationContext = () => useContext( NavigationContext ); diff --git a/packages/components/src/navigation/group/context.js b/packages/components/src/navigation/group/context.js new file mode 100644 index 00000000000000..d6725504ba8f2c --- /dev/null +++ b/packages/components/src/navigation/group/context.js @@ -0,0 +1,9 @@ +/** + * WordPress dependencies + */ +import { createContext, useContext } from '@wordpress/element'; + +export const NavigationGroupContext = createContext( { group: undefined } ); + +export const useNavigationGroupContext = () => + useContext( NavigationGroupContext ); diff --git a/packages/components/src/navigation/group/index.js b/packages/components/src/navigation/group/index.js index ba2b3a2ad13b91..ec2282d2f1e1f3 100644 --- a/packages/components/src/navigation/group/index.js +++ b/packages/components/src/navigation/group/index.js @@ -2,35 +2,60 @@ * External dependencies */ import classnames from 'classnames'; +import { find, uniqueId } from 'lodash'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; /** * Internal dependencies */ +import { NavigationGroupContext } from './context'; import { GroupTitleUI } from '../styles/navigation-styles'; -import { useNavigationMenuContext } from '../menu/context'; +import { useNavigationContext } from '../context'; +import { useRTL } from '../../utils/rtl'; export default function NavigationGroup( { children, className, title } ) { - const { isActive } = useNavigationMenuContext(); + const [ groupId ] = useState( uniqueId( 'group-' ) ); + const { + navigationTree: { items }, + } = useNavigationContext(); + const isRTL = useRTL(); + + const context = { group: groupId }; - // Keep the children rendered to make sure inactive items are included in the navigation tree - if ( ! isActive ) { - return children; + // Keep the children rendered to make sure invisible items are included in the navigation tree. + if ( ! find( items, { group: groupId, _isVisible: true } ) ) { + return ( + <NavigationGroupContext.Provider value={ context }> + { children } + </NavigationGroupContext.Provider> + ); } + const groupTitleId = `components-navigation__group-title-${ groupId }`; const classes = classnames( 'components-navigation__group', className ); return ( - <li className={ classes }> - { title && ( - <GroupTitleUI - as="h3" - className="components-navigation__group-title" - variant="caption" - > - { title } - </GroupTitleUI> - ) } - <ul>{ children }</ul> - </li> + <NavigationGroupContext.Provider value={ context }> + <li className={ classes }> + { title && ( + <GroupTitleUI + as="h3" + className="components-navigation__group-title" + id={ groupTitleId } + variant="caption" + isRTL={ isRTL } + > + { title } + </GroupTitleUI> + ) } + <ul aria-labelledby={ groupTitleId } role="group"> + { children } + </ul> + </li> + </NavigationGroupContext.Provider> ); } diff --git a/packages/components/src/navigation/index.js b/packages/components/src/navigation/index.js index 3204a84e92b0f4..859a20319ccec3 100644 --- a/packages/components/src/navigation/index.js +++ b/packages/components/src/navigation/index.js @@ -12,11 +12,12 @@ import { useEffect, useRef, useState } from '@wordpress/element'; /** * Internal dependencies */ -import Animate from '../animate'; +import { getAnimateClassName } from '../animate'; import { ROOT_MENU } from './constants'; import { NavigationContext } from './context'; import { NavigationUI } from './styles/navigation-styles'; import { useCreateNavigationTree } from './use-create-navigation-tree'; +import { useRTL } from '../utils/rtl'; export default function Navigation( { activeItem, @@ -28,8 +29,9 @@ export default function Navigation( { const [ menu, setMenu ] = useState( activeMenu ); const [ slideOrigin, setSlideOrigin ] = useState(); const navigationTree = useCreateNavigationTree(); + const defaultSlideOrigin = useRTL() ? 'right' : 'left'; - const setActiveMenu = ( menuId, slideInOrigin = 'left' ) => { + const setActiveMenu = ( menuId, slideInOrigin = defaultSlideOrigin ) => { if ( ! navigationTree.getMenu( menuId ) ) { return; } @@ -61,27 +63,23 @@ export default function Navigation( { }; const classes = classnames( 'components-navigation', className ); + const animateClassName = getAnimateClassName( { + type: 'slide-in', + origin: slideOrigin, + } ); return ( <NavigationUI className={ classes }> - <Animate + <div key={ menu } - type="slide-in" - options={ { origin: slideOrigin } } + className={ classnames( { + [ animateClassName ]: isMounted.current && slideOrigin, + } ) } > - { ( { className: animateClassName } ) => ( - <div - className={ classnames( { - [ animateClassName ]: - isMounted.current && slideOrigin, - } ) } - > - <NavigationContext.Provider value={ context }> - { children } - </NavigationContext.Provider> - </div> - ) } - </Animate> + <NavigationContext.Provider value={ context }> + { children } + </NavigationContext.Provider> + </div> </NavigationUI> ); } diff --git a/packages/components/src/navigation/item/base-content.js b/packages/components/src/navigation/item/base-content.js new file mode 100644 index 00000000000000..93c8aef9193391 --- /dev/null +++ b/packages/components/src/navigation/item/base-content.js @@ -0,0 +1,34 @@ +/** + * Internal dependencies + */ +import { useRTL } from '../../utils'; +import { ItemBadgeUI, ItemTitleUI } from '../styles/navigation-styles'; + +export default function NavigationItemBaseContent( props ) { + const { badge, title } = props; + const isRTL = useRTL(); + + return ( + <> + { title && ( + <ItemTitleUI + className="components-navigation__item-title" + variant="body.small" + isRTL={ isRTL } + as="span" + > + { title } + </ItemTitleUI> + ) } + + { badge && ( + <ItemBadgeUI + className="components-navigation__item-badge" + isRTL={ isRTL } + > + { badge } + </ItemBadgeUI> + ) } + </> + ); +} diff --git a/packages/components/src/navigation/item/base.js b/packages/components/src/navigation/item/base.js new file mode 100644 index 00000000000000..f430a00087fded --- /dev/null +++ b/packages/components/src/navigation/item/base.js @@ -0,0 +1,38 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { uniqueId } from 'lodash'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { useNavigationContext } from '../context'; +import { useNavigationTreeItem } from './use-navigation-tree-item'; +import { ItemBaseUI } from '../styles/navigation-styles'; + +export default function NavigationItemBase( props ) { + const { children, className, ...restProps } = props; + + const [ itemId ] = useState( uniqueId( 'item-' ) ); + + useNavigationTreeItem( itemId, props ); + const { navigationTree } = useNavigationContext(); + + if ( ! navigationTree.getItem( itemId )?._isVisible ) { + return null; + } + + const classes = classnames( 'components-navigation__item', className ); + + return ( + <ItemBaseUI className={ classes } { ...restProps }> + { children } + </ItemBaseUI> + ); +} diff --git a/packages/components/src/navigation/item/index.js b/packages/components/src/navigation/item/index.js index 7bedd7cd72f5db..4514633b3d4593 100644 --- a/packages/components/src/navigation/item/index.js +++ b/packages/components/src/navigation/item/index.js @@ -7,16 +7,17 @@ import { noop } from 'lodash'; /** * WordPress dependencies */ -import { Icon, chevronRight } from '@wordpress/icons'; +import { Icon, chevronLeft, chevronRight } from '@wordpress/icons'; /** * Internal dependencies */ import Button from '../../button'; import { useNavigationContext } from '../context'; -import { ItemBadgeUI, ItemTitleUI, ItemUI } from '../styles/navigation-styles'; -import { useNavigationTreeItem } from './use-navigation-tree-item'; -import { useNavigationMenuContext } from '../menu/context'; +import { ItemUI } from '../styles/navigation-styles'; +import { useRTL } from '../../utils/rtl'; +import NavigationItemBaseContent from './base-content'; +import NavigationItemBase from './base'; export default function NavigationItem( props ) { const { @@ -28,20 +29,30 @@ export default function NavigationItem( props ) { navigateToMenu, onClick = noop, title, + hideIfTargetMenuEmpty, + isText, ...restProps } = props; - useNavigationTreeItem( props ); - const { activeItem, setActiveMenu } = useNavigationContext(); - const { isActive } = useNavigationMenuContext(); - // If this item is in an inactive menu, then we skip rendering - // We need to make sure this component gets mounted though - // To make sure inactive items are included in the navigation tree - if ( ! isActive ) { + const { + activeItem, + setActiveMenu, + navigationTree: { isMenuEmpty }, + } = useNavigationContext(); + const isRTL = useRTL(); + + // If hideIfTargetMenuEmpty prop is true + // And the menu we are supposed to navigate to + // Is marked as empty, then we skip rendering the item + if ( + hideIfTargetMenuEmpty && + navigateToMenu && + isMenuEmpty( navigateToMenu ) + ) { return null; } - const classes = classnames( 'components-navigation__item', className, { + const classes = classnames( className, { 'is-active': item && activeItem === item, } ); @@ -52,30 +63,23 @@ export default function NavigationItem( props ) { onClick( event ); }; + const icon = isRTL ? chevronLeft : chevronRight; + const baseProps = isText + ? restProps + : { as: Button, href, onClick: onItemClick, ...restProps }; return ( - <ItemUI className={ classes }> + <NavigationItemBase { ...props } className={ classes }> { children || ( - <Button href={ href } onClick={ onItemClick } { ...restProps }> - { title && ( - <ItemTitleUI - className="components-navigation__item-title" - variant="body.small" - as="span" - > - { title } - </ItemTitleUI> - ) } - - { badge && ( - <ItemBadgeUI className="components-navigation__item-badge"> - { badge } - </ItemBadgeUI> - ) } + <ItemUI { ...baseProps }> + <NavigationItemBaseContent + title={ title } + badge={ badge } + /> - { navigateToMenu && <Icon icon={ chevronRight } /> } - </Button> + { navigateToMenu && <Icon icon={ icon } /> } + </ItemUI> ) } - </ItemUI> + </NavigationItemBase> ); } diff --git a/packages/components/src/navigation/item/use-navigation-tree-item.js b/packages/components/src/navigation/item/use-navigation-tree-item.js index b501da08d08b68..109c7a38bf9246 100644 --- a/packages/components/src/navigation/item/use-navigation-tree-item.js +++ b/packages/components/src/navigation/item/use-navigation-tree-item.js @@ -7,20 +7,32 @@ import { useEffect } from '@wordpress/element'; * Internal dependencies */ import { useNavigationContext } from '../context'; +import { useNavigationGroupContext } from '../group/context'; import { useNavigationMenuContext } from '../menu/context'; +import { normalizedSearch } from '../utils'; -export const useNavigationTreeItem = ( props ) => { +export const useNavigationTreeItem = ( itemId, props ) => { const { + activeMenu, navigationTree: { addItem, removeItem }, } = useNavigationContext(); - const { menu } = useNavigationMenuContext(); + const { group } = useNavigationGroupContext(); + const { menu, search } = useNavigationMenuContext(); - const key = props.item; useEffect( () => { - addItem( key, { ...props, menu } ); + const isMenuActive = activeMenu === menu; + const isItemVisible = + ! search || normalizedSearch( props.title, search ); + + addItem( itemId, { + ...props, + group, + menu, + _isVisible: isMenuActive && isItemVisible, + } ); return () => { - removeItem( key ); + removeItem( itemId ); }; - }, [] ); + }, [ activeMenu, search ] ); }; diff --git a/packages/components/src/navigation/menu/context.js b/packages/components/src/navigation/menu/context.js index 485ceec98c14ec..29b4814757c7cd 100644 --- a/packages/components/src/navigation/menu/context.js +++ b/packages/components/src/navigation/menu/context.js @@ -5,7 +5,7 @@ import { createContext, useContext } from '@wordpress/element'; export const NavigationMenuContext = createContext( { menu: undefined, - isActive: false, + search: '', } ); export const useNavigationMenuContext = () => useContext( NavigationMenuContext ); diff --git a/packages/components/src/navigation/menu/index.js b/packages/components/src/navigation/menu/index.js index 2e298eeddc9c9e..0890becbfb26cf 100644 --- a/packages/components/src/navigation/menu/index.js +++ b/packages/components/src/navigation/menu/index.js @@ -3,39 +3,49 @@ */ import classnames from 'classnames'; +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + /** * Internal dependencies */ import { ROOT_MENU } from '../constants'; -import { useNavigationContext } from '../context'; -import { MenuTitleUI, MenuUI } from '../styles/navigation-styles'; -import NavigationBackButton from '../back-button'; import { NavigationMenuContext } from './context'; +import { useNavigationContext } from '../context'; import { useNavigationTreeMenu } from './use-navigation-tree-menu'; +import NavigationBackButton from '../back-button'; +import NavigationMenuTitle from './menu-title'; +import NavigationSearchNoResultsFound from './search-no-results-found'; +import { NavigableMenu } from '../../navigable-container'; +import { MenuUI } from '../styles/navigation-styles'; export default function NavigationMenu( props ) { const { backButtonLabel, children, className, + hasSearch, menu = ROOT_MENU, + onBackButtonClick, + onSearch: setControlledSearch, parentMenu, + search: controlledSearch, title, - onBackButtonClick, + titleAction, } = props; + const [ uncontrolledSearch, setUncontrolledSearch ] = useState( '' ); useNavigationTreeMenu( props ); const { activeMenu } = useNavigationContext(); - const isActive = activeMenu === menu; - - const classes = classnames( 'components-navigation__menu', className ); const context = { menu, - isActive, + search: uncontrolledSearch, }; - // Keep the children rendered to make sure inactive items are included in the navigation tree - if ( ! isActive ) { + // Keep the children rendered to make sure invisible items are included in the navigation tree + if ( activeMenu !== menu ) { return ( <NavigationMenuContext.Provider value={ context }> { children } @@ -43,6 +53,15 @@ export default function NavigationMenu( props ) { ); } + const isControlledSearch = !! setControlledSearch; + const search = isControlledSearch ? controlledSearch : uncontrolledSearch; + const onSearch = isControlledSearch + ? setControlledSearch + : setUncontrolledSearch; + + const menuTitleId = `components-navigation__menu-title-${ menu }`; + const classes = classnames( 'components-navigation__menu', className ); + return ( <NavigationMenuContext.Provider value={ context }> <MenuUI className={ classes }> @@ -53,16 +72,23 @@ export default function NavigationMenu( props ) { onClick={ onBackButtonClick } /> ) } - { title && ( - <MenuTitleUI - as="h2" - className="components-navigation__menu-title" - variant="subtitle" - > - { title } - </MenuTitleUI> - ) } - <ul>{ children }</ul> + + <NavigationMenuTitle + hasSearch={ hasSearch } + onSearch={ onSearch } + search={ search } + title={ title } + titleAction={ titleAction } + /> + + <NavigableMenu> + <ul aria-labelledby={ menuTitleId }> + { children } + { search && ( + <NavigationSearchNoResultsFound search={ search } /> + ) } + </ul> + </NavigableMenu> </MenuUI> </NavigationMenuContext.Provider> ); diff --git a/packages/components/src/navigation/menu/menu-title-search.js b/packages/components/src/navigation/menu/menu-title-search.js new file mode 100644 index 00000000000000..02b87e8b089f14 --- /dev/null +++ b/packages/components/src/navigation/menu/menu-title-search.js @@ -0,0 +1,113 @@ +/** + * External dependencies + */ +import { filter } from 'lodash'; + +/** + * WordPress dependencies + */ +import { useEffect, useRef } from '@wordpress/element'; +import { Icon, closeSmall, search as searchIcon } from '@wordpress/icons'; +import { __, _n, sprintf } from '@wordpress/i18n'; +import { ESCAPE } from '@wordpress/keycodes'; + +/** + * Internal dependencies + */ +import Button from '../../button'; +import VisuallyHidden from '../../visually-hidden'; +import withSpokenMessages from '../../higher-order/with-spoken-messages'; +import { useNavigationMenuContext } from './context'; +import { useNavigationContext } from '../context'; +import { MenuTitleSearchUI } from '../styles/navigation-styles'; +import { SEARCH_FOCUS_DELAY } from '../constants'; + +function MenuTitleSearch( { + debouncedSpeak, + onCloseSearch, + onSearch, + search, + title, +} ) { + const { + navigationTree: { items }, + } = useNavigationContext(); + const { menu } = useNavigationMenuContext(); + const inputRef = useRef(); + + // Wait for the slide-in animation to complete before autofocusing the input. + // This prevents scrolling to the input during the animation. + useEffect( () => { + const delayedFocus = setTimeout( () => { + inputRef.current.focus(); + }, SEARCH_FOCUS_DELAY ); + + return () => { + clearTimeout( delayedFocus ); + }; + }, [] ); + + useEffect( () => { + if ( ! search ) { + return; + } + + const count = filter( items, '_isVisible' ).length; + const resultsFoundMessage = sprintf( + /* translators: %d: number of results. */ + _n( '%d result found.', '%d results found.', count ), + count + ); + debouncedSpeak( resultsFoundMessage ); + }, [ items, search ] ); + + const onClose = () => { + onSearch( '' ); + onCloseSearch(); + }; + + function onKeyDown( event ) { + if ( event.keyCode === ESCAPE ) { + event.stopPropagation(); + onClose(); + } + } + + const menuTitleId = `components-navigation__menu-title-${ menu }`; + const inputId = `components-navigation__menu-title-search-${ menu }`; + /* translators: placeholder for menu search box. %s: menu title */ + const placeholder = sprintf( __( 'Search in %s' ), title ); + + return ( + <MenuTitleSearchUI className="components-navigation__menu-title-search"> + <Icon icon={ searchIcon } /> + + <VisuallyHidden as="label" htmlFor={ inputId } id={ menuTitleId }> + { placeholder } + </VisuallyHidden> + + <input + autoComplete="off" + className="components-text-control__input" + id={ inputId } + onChange={ ( event ) => onSearch( event.target.value ) } + onKeyDown={ onKeyDown } + placeholder={ placeholder } + ref={ inputRef } + type="search" + value={ search } + /> + + <Button + isSmall + isTertiary + label={ __( 'Close search' ) } + onClick={ onClose } + > + <Icon icon={ closeSmall } /> + </Button> + </MenuTitleSearchUI> + ); +} + +export default withSpokenMessages( MenuTitleSearch ); diff --git a/packages/components/src/navigation/menu/menu-title.js b/packages/components/src/navigation/menu/menu-title.js new file mode 100644 index 00000000000000..7d36f01cf85d19 --- /dev/null +++ b/packages/components/src/navigation/menu/menu-title.js @@ -0,0 +1,101 @@ +/** + * WordPress dependencies + */ +import { useRef, useState } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; +import { Icon, search as searchIcon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import { getAnimateClassName } from '../../animate'; +import Button from '../../button'; +import MenuTitleSearch from './menu-title-search'; +import { + MenuTitleActionsUI, + MenuTitleHeadingUI, + MenuTitleUI, +} from '../styles/navigation-styles'; +import { useNavigationMenuContext } from './context'; +import { SEARCH_FOCUS_DELAY } from '../constants'; +import { useRTL } from '../../utils/rtl'; + +export default function NavigationMenuTitle( { + hasSearch, + onSearch, + search, + title, + titleAction, +} ) { + const [ isSearching, setIsSearching ] = useState( false ); + const { menu } = useNavigationMenuContext(); + const searchButtonRef = useRef(); + const isRTL = useRTL(); + + if ( ! title ) { + return null; + } + + const onCloseSearch = () => { + setIsSearching( false ); + + // Wait for the slide-in animation to complete before focusing the search button. + // eslint-disable-next-line @wordpress/react-no-unsafe-timeout + setTimeout( () => { + searchButtonRef.current.focus(); + }, SEARCH_FOCUS_DELAY ); + }; + + const menuTitleId = `components-navigation__menu-title-${ menu }`; + /* translators: search button label for menu search box. %s: menu title */ + const searchButtonLabel = sprintf( __( 'Search in %s' ), title ); + + return ( + <MenuTitleUI className="components-navigation__menu-title"> + { ! isSearching && ( + <MenuTitleHeadingUI + as="h2" + className="components-navigation__menu-title-heading" + variant="title.small" + isRTL={ isRTL } + > + <span id={ menuTitleId }>{ title }</span> + + { ( hasSearch || titleAction ) && ( + <MenuTitleActionsUI> + { titleAction } + + { hasSearch && ( + <Button + isSmall + isTertiary + label={ searchButtonLabel } + onClick={ () => setIsSearching( true ) } + ref={ searchButtonRef } + > + <Icon icon={ searchIcon } /> + </Button> + ) } + </MenuTitleActionsUI> + ) } + </MenuTitleHeadingUI> + ) } + + { isSearching && ( + <div + className={ getAnimateClassName( { + type: 'slide-in', + origin: 'left', + } ) } + > + <MenuTitleSearch + onCloseSearch={ onCloseSearch } + onSearch={ onSearch } + search={ search } + title={ title } + /> + </div> + ) } + </MenuTitleUI> + ); +} diff --git a/packages/components/src/navigation/menu/search-no-results-found.js b/packages/components/src/navigation/menu/search-no-results-found.js new file mode 100644 index 00000000000000..07391714c8dff3 --- /dev/null +++ b/packages/components/src/navigation/menu/search-no-results-found.js @@ -0,0 +1,33 @@ +/** + * External dependencies + */ +import { filter } from 'lodash'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { useNavigationContext } from '../context'; +import { ItemBaseUI, ItemUI } from '../styles/navigation-styles'; + +export default function NavigationSearchNoResultsFound( { search } ) { + const { + navigationTree: { items }, + } = useNavigationContext(); + + const resultsCount = filter( items, '_isVisible' ).length; + + if ( ! search || !! resultsCount ) { + return null; + } + + return ( + <ItemBaseUI> + <ItemUI>{ __( 'No results found.' ) } </ItemUI> + </ItemBaseUI> + ); +} diff --git a/packages/components/src/navigation/stories/group.js b/packages/components/src/navigation/stories/group.js new file mode 100644 index 00000000000000..1c919d50464f23 --- /dev/null +++ b/packages/components/src/navigation/stories/group.js @@ -0,0 +1,47 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import Navigation from '..'; +import NavigationItem from '../item'; +import NavigationMenu from '../menu'; +import NavigationGroup from '../group'; + +export function GroupStory() { + const [ activeItem, setActiveItem ] = useState( 'item-1' ); + + return ( + <Navigation activeItem={ activeItem } className="navigation-story"> + <NavigationMenu title="Home"> + <NavigationGroup title="Group 1"> + <NavigationItem + item="item-1" + onClick={ () => setActiveItem( 'item-1' ) } + title="Item 1" + /> + <NavigationItem + item="item-2" + onClick={ () => setActiveItem( 'item-2' ) } + title="Item 2" + /> + </NavigationGroup> + <NavigationGroup title="Group 2"> + <NavigationItem + item="item-3" + onClick={ () => setActiveItem( 'item-3' ) } + title="Item 3" + /> + <NavigationItem + item="item-4" + onClick={ () => setActiveItem( 'item-4' ) } + title="Item 4" + /> + </NavigationGroup> + </NavigationMenu> + </Navigation> + ); +} diff --git a/packages/components/src/navigation/stories/hide-if-empty.js b/packages/components/src/navigation/stories/hide-if-empty.js new file mode 100644 index 00000000000000..0977a41ab18463 --- /dev/null +++ b/packages/components/src/navigation/stories/hide-if-empty.js @@ -0,0 +1,57 @@ +/** + * Internal dependencies + */ +import Navigation from '..'; +import NavigationItem from '../item'; +import NavigationMenu from '../menu'; + +export function HideIfEmptyStory() { + return ( + <> + <Navigation className="navigation-story"> + <NavigationMenu title="Home" menu="root" isEmpty={ false }> + <NavigationItem + navigateToMenu="root-sub-1" + title="To sub 1 (hidden)" + hideIfTargetMenuEmpty + /> + + <NavigationItem + navigateToMenu="root-sub-2" + title="To sub 2 (visible)" + hideIfTargetMenuEmpty + /> + + <NavigationItem + navigateToMenu="root-sub-1-sub-1" + title="To sub 1-1 (hidden)" + hideIfTargetMenuEmpty + /> + </NavigationMenu> + + <NavigationMenu + menu="root-sub-1" + parentMenu="root" + isEmpty={ true } + /> + <NavigationMenu + menu="root-sub-2" + parentMenu="root" + isEmpty={ false } + > + <NavigationItem title="This menu is visible" /> + </NavigationMenu> + <NavigationMenu + menu="root-sub-1-sub-1" + parentMenu="root-sub-1" + isEmpty={ true } + /> + </Navigation> + + <p> + This story contains 3 navigation items and 4 menus. You should + only see one item: <strong>To sub 2 (visible)</strong> + </p> + </> + ); +} diff --git a/packages/components/src/navigation/stories/index.js b/packages/components/src/navigation/stories/index.js index 09094c914cc939..2b8cb59a906619 100644 --- a/packages/components/src/navigation/stories/index.js +++ b/packages/components/src/navigation/stories/index.js @@ -7,8 +7,11 @@ import NavigationGroup from '../group'; import NavigationItem from '../item'; import NavigationMenu from '../menu'; import { DefaultStory } from './default'; +import { GroupStory } from './group'; import { ControlledStateStory } from './controlled-state'; +import { SearchStory } from './search'; import { MoreExamplesStory } from './more-examples'; +import { HideIfEmptyStory } from './hide-if-empty'; import './style.css'; export default { @@ -24,4 +27,7 @@ export default { export const _default = () => <DefaultStory />; export const controlledState = () => <ControlledStateStory />; +export const groups = () => <GroupStory />; +export const search = () => <SearchStory />; export const moreExamples = () => <MoreExamplesStory />; +export const hideIfEmpty = () => <HideIfEmptyStory />; diff --git a/packages/components/src/navigation/stories/more-examples.js b/packages/components/src/navigation/stories/more-examples.js index 5cde135c0a7643..8795e8f0284f41 100644 --- a/packages/components/src/navigation/stories/more-examples.js +++ b/packages/components/src/navigation/stories/more-examples.js @@ -79,6 +79,19 @@ export function MoreExamplesStory() { </a> </NavigationItem> </NavigationGroup> + <NavigationGroup title="Text only items"> + <NavigationItem + item="item-text-only" + title="This is just text, doesn't have any functionality" + isText + /> + <NavigationItem + item="item-text-only-with-badge" + title="Text with badge" + badge="2" + isText + /> + </NavigationGroup> </NavigationMenu> <NavigationMenu diff --git a/packages/components/src/navigation/stories/search.js b/packages/components/src/navigation/stories/search.js new file mode 100644 index 00000000000000..d9bcd405cbc349 --- /dev/null +++ b/packages/components/src/navigation/stories/search.js @@ -0,0 +1,77 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import Navigation from '..'; +import NavigationGroup from '../group'; +import NavigationItem from '../item'; +import NavigationMenu from '../menu'; +import { normalizedSearch } from '../utils'; + +const searchItems = [ + { item: 'foo', title: 'Foo' }, + { item: 'bar', title: 'Bar' }, + { item: 'baz', title: 'Baz' }, + { item: 'qux', title: 'Qux' }, + { item: 'quux', title: 'Quux' }, + { item: 'corge', title: 'Corge' }, + { item: 'grault', title: 'Grault' }, + { item: 'garply', title: 'Garply' }, + { item: 'waldo', title: 'Waldo' }, +]; + +export function SearchStory() { + const [ activeItem, setActiveItem ] = useState( 'item-1' ); + const [ search, setSearch ] = useState( '' ); + + return ( + <Navigation activeItem={ activeItem } className="navigation-story"> + <NavigationMenu hasSearch title="Home"> + <NavigationGroup title="Items"> + { searchItems.map( ( { item, title } ) => ( + <NavigationItem + item={ `item-${ item }` } + key={ `item-${ item }` } + onClick={ () => setActiveItem( `item-${ item }` ) } + title={ title } + /> + ) ) } + </NavigationGroup> + + <NavigationGroup title="More Examples"> + <NavigationItem + item="item-controlled-search" + navigateToMenu="controlled-search" + title="Controlled Search" + /> + </NavigationGroup> + </NavigationMenu> + + <NavigationMenu + hasSearch + menu="controlled-search" + onSearch={ ( value ) => setSearch( value ) } + parentMenu="root" + search={ search } + title="Controlled Search" + > + { searchItems + .filter( ( { title } ) => + normalizedSearch( title, search ) + ) + .map( ( { item, title } ) => ( + <NavigationItem + item={ `child-${ item }` } + key={ `child-${ item }` } + onClick={ () => setActiveItem( `child-${ item }` ) } + title={ title } + /> + ) ) } + </NavigationMenu> + </Navigation> + ); +} diff --git a/packages/components/src/navigation/stories/style.css b/packages/components/src/navigation/stories/style.css index a702a797b771ad..bcb4e0cbf1fc19 100644 --- a/packages/components/src/navigation/stories/style.css +++ b/packages/components/src/navigation/stories/style.css @@ -14,6 +14,12 @@ fill: #949494; margin-right: 8px; } + +:root[dir="rtl"] .navigation-story__wordpress-icon svg { + fill: #949494; + margin-right: 0; + margin-left: 8px; +} .navigation-story__wordpress-icon:hover svg { fill: #ddd; } diff --git a/packages/components/src/navigation/styles/navigation-styles.js b/packages/components/src/navigation/styles/navigation-styles.js index 93c347a54ca250..65d2c1f2151def 100644 --- a/packages/components/src/navigation/styles/navigation-styles.js +++ b/packages/components/src/navigation/styles/navigation-styles.js @@ -9,19 +9,20 @@ import styled from '@emotion/styled'; import { G2, UI } from '../../utils/colors-values'; import Button from '../../button'; import Text from '../../text'; -import { reduceMotion } from '../../utils'; +import { reduceMotion, space } from '../../utils'; export const NavigationUI = styled.div` width: 100%; background-color: ${ G2.darkGray.primary }; + box-sizing: border-box; color: #f0f0f0; - padding: 0 8px; + padding: 0 ${ space( 1 ) }; overflow: hidden; `; export const MenuUI = styled.div` - margin-top: 24px; - margin-bottom: 24px; + margin-top: ${ space( 3 ) }; + margin-bottom: ${ space( 3 ) }; display: flex; flex-direction: column; ul { @@ -30,7 +31,11 @@ export const MenuUI = styled.div` list-style: none; } .components-navigation__back-button { - margin-bottom: 24px; + margin-bottom: ${ space( 3 ) }; + } + + .components-navigation__group + .components-navigation__group { + margin-top: ${ space( 3 ) }; } `; @@ -50,34 +55,105 @@ export const MenuBackButtonUI = styled( Button )` } `; -export const MenuTitleUI = styled( Text )` - padding: 4px 0 4px 16px; - margin-bottom: 8px; +export const MenuTitleUI = styled.div` + overflow: hidden; + width: 100%; +`; + +export const MenuTitleHeadingUI = styled( Text )` + align-items: center; color: ${ G2.gray[ 100 ] }; + display: flex; + justify-content: space-between; + margin-bottom: ${ space( 1 ) }; + padding: ${ ( props ) => + props.isRTL + ? `${ space( 0.5 ) } ${ space( 2 ) } ${ space( 0.5 ) } ${ space( + 1.5 + ) }` + : `${ space( 0.5 ) } ${ space( 1.5 ) } ${ space( 0.5 ) } ${ space( + 2 + ) }` }; +`; + +export const MenuTitleActionsUI = styled.span` + height: ${ space( 3 ) }; // 24px, same height as the buttons inside + + .components-button.is-small { + color: ${ G2.lightGray.ui }; + margin-right: ${ space( 0.5 ) }; // Avoid hiding the focus outline + padding: 0; + + &:active:not( :disabled ) { + background: none; + color: ${ G2.gray[ 200 ] }; + } + &:hover:not( :disabled ) { + box-shadow: none; + color: ${ G2.gray[ 200 ] }; + } + } +`; + +export const MenuTitleSearchUI = styled.div` + padding: 0; + position: relative; + + input { + height: ${ space( 4.5 ) }; // 36px, same height as MenuTitle + margin-bottom: ${ space( 1 ) }; + padding-left: ${ space( 4 ) }; // Leave room for the search icon + padding-right: ${ space( + 4 + ) }; // Leave room for the close search button + + &::-webkit-search-decoration, + &::-webkit-search-cancel-button, + &::-webkit-search-results-button, + &::-webkit-search-results-decoration { + -webkit-appearance: none; + } + } + + > svg { + left: ${ space( 0.5 ) }; + position: absolute; + top: 6px; + } + + .components-button.is-small { + height: 30px; + padding: 0; + position: absolute; + right: ${ space( 1 ) }; + top: 3px; + + &:active:not( :disabled ) { + background: none; + } + &:hover:not( :disabled ) { + box-shadow: none; + } + } `; export const GroupTitleUI = styled( Text )` - margin-top: 8px; - padding: 4px 0 4px 16px; + margin-top: ${ space( 1 ) }; + padding: ${ ( props ) => + props.isRTL + ? `${ space( 0.5 ) } ${ space( 2 ) } ${ space( 0.5 ) } 0` + : `${ space( 0.5 ) } 0 ${ space( 0.5 ) } ${ space( 2 ) }` }; text-transform: uppercase; color: ${ G2.gray[ 100 ] }; `; -export const ItemUI = styled.li` +export const ItemBaseUI = styled.li` border-radius: 2px; color: ${ G2.lightGray.ui }; + margin-bottom: 0; button, a { - margin: 0; - font-weight: 400; - font-size: 14px; - line-height: 20px; - padding-left: 16px; - padding-right: 16px; - width: 100%; - color: ${ G2.lightGray.ui }; - &:hover, &:focus:not( [aria-disabled='true'] ):active, &:active:not( [aria-disabled='true'] ):active { @@ -100,10 +176,24 @@ export const ItemUI = styled.li` } `; +export const ItemUI = styled.div` + display: flex; + align-items: center; + height: auto; + min-height: 32px; + margin: 0; + padding: ${ space( 0.75 ) } ${ space( 2 ) }; + font-weight: 400; + line-height: 20px; + width: 100%; + color: ${ G2.lightGray.ui }; +`; + export const ItemBadgeUI = styled.span` - margin-left: 8px; + margin-left: ${ ( props ) => ( props.isRTL ? '0' : space( 1 ) ) }; + margin-right: ${ ( props ) => ( props.isRTL ? space( 1 ) : '0' ) }; display: inline-flex; - padding: 4px 12px; + padding: ${ space( 0.5 ) } ${ space( 1.5 ) }; border-radius: 2px; animation: fade-in 250ms ease-out; @@ -120,6 +210,7 @@ export const ItemBadgeUI = styled.span` `; export const ItemTitleUI = styled( Text )` - margin-right: auto; - text-align: left; + ${ ( props ) => + props.isRTL ? 'margin-left: auto;' : 'margin-right: auto;' } + font-size: 13px; `; diff --git a/packages/components/src/navigation/use-create-navigation-tree.js b/packages/components/src/navigation/use-create-navigation-tree.js index 52414e540dd624..63d5d4a6e2bbb9 100644 --- a/packages/components/src/navigation/use-create-navigation-tree.js +++ b/packages/components/src/navigation/use-create-navigation-tree.js @@ -1,3 +1,8 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + /** * Internal dependencies */ @@ -18,6 +23,50 @@ export const useCreateNavigationTree = () => { removeNode: removeMenu, } = useNavigationTreeNodes(); + /** + * Stores direct nested menus of menus + * This makes it easy to traverse menu tree + * + * Key is the menu prop of the menu + * Value is an array of menu keys + */ + const [ childMenu, setChildMenu ] = useState( {} ); + const getChildMenu = ( menu ) => childMenu[ menu ] || []; + + const traverseMenu = ( startMenu, callback ) => { + const visited = []; + let queue = [ startMenu ]; + let current; + + while ( queue.length > 0 ) { + current = getMenu( queue.shift() ); + + if ( ! current || visited.includes( current.menu ) ) { + continue; + } + + visited.push( current.menu ); + queue = [ ...queue, ...getChildMenu( current.menu ) ]; + + if ( callback( current ) === false ) { + break; + } + } + }; + + const isMenuEmpty = ( menuToCheck ) => { + let isEmpty = true; + + traverseMenu( menuToCheck, ( current ) => { + if ( ! current.isEmpty ) { + isEmpty = false; + return false; + } + } ); + + return isEmpty; + }; + return { items, getItem, @@ -26,7 +75,24 @@ export const useCreateNavigationTree = () => { menus, getMenu, - addMenu, + addMenu: ( key, value ) => { + setChildMenu( ( state ) => { + const newState = { ...state }; + + if ( ! newState[ value.parentMenu ] ) { + newState[ value.parentMenu ] = []; + } + + newState[ value.parentMenu ].push( key ); + + return newState; + } ); + + addMenu( key, value ); + }, removeMenu, + childMenu, + traverseMenu, + isMenuEmpty, }; }; diff --git a/packages/components/src/navigation/utils.js b/packages/components/src/navigation/utils.js new file mode 100644 index 00000000000000..251b90cfe44dfd --- /dev/null +++ b/packages/components/src/navigation/utils.js @@ -0,0 +1,11 @@ +/** + * External dependencies + */ +import { deburr } from 'lodash'; + +// @see packages/block-editor/src/components/inserter/search-items.js +export const normalizeInput = ( input ) => + deburr( input ).replace( /^\//, '' ).toLowerCase(); + +export const normalizedSearch = ( title, search ) => + -1 !== normalizeInput( title ).indexOf( normalizeInput( search ) ); diff --git a/packages/components/src/popover/detect-outside.js b/packages/components/src/popover/detect-outside.js deleted file mode 100644 index 92a3359d24055f..00000000000000 --- a/packages/components/src/popover/detect-outside.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * WordPress dependencies - */ -import { Component } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import withFocusOutside from '../higher-order/with-focus-outside'; - -class PopoverDetectOutside extends Component { - handleFocusOutside( event ) { - this.props.onFocusOutside( event ); - } - - render() { - return this.props.children; - } -} - -export default withFocusOutside( PopoverDetectOutside ); diff --git a/packages/components/src/popover/index.js b/packages/components/src/popover/index.js index 0b967b096886d8..32382b21f22319 100644 --- a/packages/components/src/popover/index.js +++ b/packages/components/src/popover/index.js @@ -2,6 +2,7 @@ * External dependencies */ import classnames from 'classnames'; +import mergeRefs from 'react-merge-refs'; /** * WordPress dependencies @@ -9,31 +10,30 @@ import classnames from 'classnames'; import { useRef, useState, - useEffect, useLayoutEffect, + useCallback, } from '@wordpress/element'; -import { focus, getRectangleFromRange } from '@wordpress/dom'; +import { getRectangleFromRange } from '@wordpress/dom'; import { ESCAPE } from '@wordpress/keycodes'; import deprecated from '@wordpress/deprecated'; -import { useViewportMatch, useResizeObserver } from '@wordpress/compose'; +import { + useViewportMatch, + useResizeObserver, + useFocusOnMount, + __experimentalUseFocusOutside as useFocusOutside, + useConstrainedTabbing, + useFocusReturn, +} from '@wordpress/compose'; import { close } from '@wordpress/icons'; /** * Internal dependencies */ import { computePopoverPosition } from './utils'; -import withFocusReturn from '../higher-order/with-focus-return'; -import withConstrainedTabbing from '../higher-order/with-constrained-tabbing'; -import PopoverDetectOutside from './detect-outside'; import Button from '../button'; import ScrollLock from '../scroll-lock'; -import IsolatedEventContainer from '../isolated-event-container'; import { Slot, Fill, useSlot } from '../slot-fill'; -import Animate from '../animate'; - -const FocusManaged = withConstrainedTabbing( - withFocusReturn( ( { children } ) => children ) -); +import { getAnimateClassName } from '../animate'; /** * Name of slot in which popover should fill. @@ -71,11 +71,17 @@ function computeAnchorRect( return; } - if ( anchorRef instanceof window.Range ) { + // Duck-type to check if `anchorRef` is an instance of Range + // `anchorRef instanceof window.Range` checks will break across document boundaries + // such as in an iframe + if ( typeof anchorRef?.cloneRange === 'function' ) { return getRectangleFromRange( anchorRef ); } - if ( anchorRef instanceof window.Element ) { + // Duck-type to check if `anchorRef` is an instance of Element + // `anchorRef instanceof window.Element` checks will break across document boundaries + // such as in an iframe + if ( typeof anchorRef?.getBoundingClientRect === 'function' ) { const rect = anchorRef.getBoundingClientRect(); if ( shouldAnchorIncludePadding ) { @@ -144,53 +150,6 @@ function withoutPadding( rect, element ) { }; } -/** - * Hook used to focus the first tabbable element on mount. - * - * @param {boolean|string} focusOnMount Focus on mount mode. - * @param {Object} contentRef Reference to the popover content element. - */ -function useFocusContentOnMount( focusOnMount, contentRef ) { - // Focus handling - useEffect( () => { - /* - * Without the setTimeout, the dom node is not being focused. Related: - * https://stackoverflow.com/questions/35522220/react-ref-with-focus-doesnt-work-without-settimeout-my-example - * - * TODO: Treat the cause, not the symptom. - */ - const focusTimeout = setTimeout( () => { - if ( ! focusOnMount || ! contentRef.current ) { - return; - } - - if ( focusOnMount === 'firstElement' ) { - // Find first tabbable node within content and shift focus, falling - // back to the popover panel itself. - const firstTabbable = focus.tabbable.find( - contentRef.current - )[ 0 ]; - - if ( firstTabbable ) { - firstTabbable.focus(); - } else { - contentRef.current.focus(); - } - - return; - } - - if ( focusOnMount === 'container' ) { - // Focus the popover panel itself so items in the popover are easily - // accessed via keyboard navigation. - contentRef.current.focus(); - } - }, 0 ); - - return () => clearTimeout( focusTimeout ); - }, [] ); -} - /** * Sets or removes an element attribute. * @@ -262,10 +221,11 @@ const Popover = ( { animate = true, onClickOutside, onFocusOutside, - __unstableSticky, + __unstableStickyBoundaryElement, __unstableSlotName = SLOT_NAME, __unstableObserveElement, __unstableBoundaryParent, + __unstableForcePosition, /* eslint-enable no-unused-vars */ ...contentProps } ) => { @@ -352,10 +312,11 @@ const Popover = ( { anchor, usedContentSize, position, - __unstableSticky, + __unstableStickyBoundaryElement, containerRef.current, relativeOffsetTop, - boundaryElement + boundaryElement, + __unstableForcePosition ); if ( @@ -451,12 +412,22 @@ const Popover = ( { shouldAnchorIncludePadding, position, contentSize, - __unstableSticky, + __unstableStickyBoundaryElement, __unstableObserveElement, __unstableBoundaryParent, ] ); - useFocusContentOnMount( focusOnMount, contentRef ); + const constrainedTabbingRef = useConstrainedTabbing(); + const focusReturnRef = useFocusReturn(); + const focusOnMountRef = useFocusOnMount( focusOnMount ); + const focusOutsideProps = useFocusOutside( handleOnFocusOutside ); + const allRefs = [ + containerRef, + focusOnMount ? constrainedTabbingRef : null, + focusOnMount ? focusReturnRef : null, + focusOnMount ? focusOnMountRef : null, + ]; + const mergedRefs = useCallback( mergeRefs( allRefs ), allRefs ); // Event handlers const maybeClose = ( event ) => { @@ -529,66 +500,59 @@ const Popover = ( { onClickOutside( clickEvent ); } + /** @type {false | string} */ + const animateClassName = + Boolean( animate && animateOrigin ) && + getAnimateClassName( { + type: 'appear', + origin: animateOrigin, + } ); + // Disable reason: We care to capture the _bubbled_ events from inputs // within popover as inferring close intent. let content = ( - <PopoverDetectOutside onFocusOutside={ handleOnFocusOutside }> - <Animate - type={ animate && animateOrigin ? 'appear' : null } - options={ { origin: animateOrigin } } - > - { ( { className: animateClassName } ) => ( - <IsolatedEventContainer - className={ classnames( - 'components-popover', - className, - animateClassName, - { - 'is-expanded': isExpanded, - 'is-without-arrow': noArrow, - 'is-alternate': isAlternate, - } - ) } - { ...contentProps } - onKeyDown={ maybeClose } - ref={ containerRef } - > - { isExpanded && <ScrollLock /> } - { isExpanded && ( - <div className="components-popover__header"> - <span className="components-popover__header-title"> - { headerTitle } - </span> - <Button - className="components-popover__close" - icon={ close } - onClick={ onClose } - /> - </div> - ) } - <div - ref={ contentRef } - className="components-popover__content" - tabIndex="-1" - > - <div style={ { position: 'relative' } }> - { containerResizeListener } - { children } - </div> - </div> - </IsolatedEventContainer> - ) } - </Animate> - </PopoverDetectOutside> + // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions + // eslint-disable-next-line jsx-a11y/no-static-element-interactions + <div + className={ classnames( + 'components-popover', + className, + animateClassName, + { + 'is-expanded': isExpanded, + 'is-without-arrow': noArrow, + 'is-alternate': isAlternate, + } + ) } + { ...contentProps } + onKeyDown={ maybeClose } + { ...focusOutsideProps } + ref={ mergedRefs } + tabIndex="-1" + > + { isExpanded && <ScrollLock /> } + { isExpanded && ( + <div className="components-popover__header"> + <span className="components-popover__header-title"> + { headerTitle } + </span> + <Button + className="components-popover__close" + icon={ close } + onClick={ onClose } + /> + </div> + ) } + <div ref={ contentRef } className="components-popover__content"> + <div style={ { position: 'relative' } }> + { containerResizeListener } + { children } + </div> + </div> + </div> ); - // Apply focus to element as long as focusOnMount is truthy; false is - // the only "disabled" value. - if ( focusOnMount ) { - content = <FocusManaged>{ content }</FocusManaged>; - } - if ( slot.ref ) { content = <Fill name={ __unstableSlotName }>{ content }</Fill>; } diff --git a/packages/components/src/popover/style.scss b/packages/components/src/popover/style.scss index 75fef0850bf4f7..44446118161c6e 100644 --- a/packages/components/src/popover/style.scss +++ b/packages/components/src/popover/style.scss @@ -180,7 +180,6 @@ $arrow-size: 8px; position: absolute; height: auto; overflow-y: auto; - min-width: 260px; } .components-popover.is-expanded & { diff --git a/packages/components/src/popover/test/__snapshots__/index.js.snap b/packages/components/src/popover/test/__snapshots__/index.js.snap index 2b8bfdd1807182..e59d209c85b129 100644 --- a/packages/components/src/popover/test/__snapshots__/index.js.snap +++ b/packages/components/src/popover/test/__snapshots__/index.js.snap @@ -3,28 +3,20 @@ exports[`Popover should pass additional props to portaled element 1`] = ` <span> <div + class="components-popover components-animate__appear is-from-top is-from-left is-without-arrow" + data-x-axis="right" + data-y-axis="bottom" + role="tooltip" + style="top: 0px; left: 0px;" tabindex="-1" > - <div> - <div> - <div - class="components-popover components-animate__appear is-from-top is-from-left is-without-arrow" - data-x-axis="right" - data-y-axis="bottom" - role="tooltip" - style="top: 0px; left: 0px;" - > - <div - class="components-popover__content" - tabindex="-1" - > - <div - style="position: relative;" - > - Hello - </div> - </div> - </div> + <div + class="components-popover__content" + > + <div + style="position: relative;" + > + Hello </div> </div> </div> @@ -34,27 +26,19 @@ exports[`Popover should pass additional props to portaled element 1`] = ` exports[`Popover should render content 1`] = ` <span> <div + class="components-popover components-animate__appear is-from-top is-from-left is-without-arrow" + data-x-axis="right" + data-y-axis="bottom" + style="top: 0px; left: 0px;" tabindex="-1" > - <div> - <div> - <div - class="components-popover components-animate__appear is-from-top is-from-left is-without-arrow" - data-x-axis="right" - data-y-axis="bottom" - style="top: 0px; left: 0px;" - > - <div - class="components-popover__content" - tabindex="-1" - > - <div - style="position: relative;" - > - Hello - </div> - </div> - </div> + <div + class="components-popover__content" + > + <div + style="position: relative;" + > + Hello </div> </div> </div> diff --git a/packages/components/src/popover/test/index.js b/packages/components/src/popover/test/index.js index d46c54cd03a94f..20d211a5b9273f 100644 --- a/packages/components/src/popover/test/index.js +++ b/packages/components/src/popover/test/index.js @@ -34,7 +34,7 @@ describe( 'Popover', () => { } ); expect( document.activeElement ).toBe( - result.container.querySelector( '.components-popover__content' ) + result.container.querySelector( '.components-popover' ) ); } ); diff --git a/packages/components/src/popover/test/utils.js b/packages/components/src/popover/test/utils.js index 131be264a4c1e6..1a97a148a50a26 100644 --- a/packages/components/src/popover/test/utils.js +++ b/packages/components/src/popover/test/utils.js @@ -154,7 +154,7 @@ describe( 'computePopoverXAxisPosition', () => { } ); } ); - it( "should set a maxWidth if there's not enough space in any direction", () => { + it( "should center popover if there's not enough space in any direction", () => { const anchorRect = { top: 10, left: 400, @@ -172,9 +172,33 @@ describe( 'computePopoverXAxisPosition', () => { expect( computePopoverXAxisPosition( anchorRect, contentSize, 'right' ) ).toEqual( { - contentWidth: 614, - popoverLeft: 410, - xAxis: 'right', + contentWidth: null, + popoverLeft: 512, + xAxis: 'center', + } ); + } ); + + it( 'should set the content width to the viewport width if content is too wide', () => { + const anchorRect = { + top: 10, + left: 400, + bottom: 30, + right: 420, + width: 20, + height: 20, + }; + + const contentSize = { + width: 1500, + height: 300, + }; + + expect( + computePopoverXAxisPosition( anchorRect, contentSize, 'right' ) + ).toEqual( { + contentWidth: 1024, + popoverLeft: 512, + xAxis: 'center', } ); } ); } ); diff --git a/packages/components/src/popover/utils.js b/packages/components/src/popover/utils.js index 867eb729550329..6f566916c66c7e 100644 --- a/packages/components/src/popover/utils.js +++ b/packages/components/src/popover/utils.js @@ -1,8 +1,3 @@ -/** - * WordPress dependencies - */ -import { getScrollContainer } from '@wordpress/dom'; - /** * Module constants */ @@ -11,15 +6,16 @@ const HEIGHT_OFFSET = 10; // used by the arrow and a bit of empty space /** * Utility used to compute the popover position over the xAxis * - * @param {Object} anchorRect Anchor Rect. - * @param {Object} contentSize Content Size. - * @param {string} xAxis Desired xAxis. - * @param {string} corner Desired corner. - * @param {boolean} sticky Whether or not to stick the popover to the - * scroll container edge when part of the anchor - * leaves view. - * @param {string} chosenYAxis yAxis to be used. - * @param {Element} boundaryElement Boundary element. + * @param {Object} anchorRect Anchor Rect. + * @param {Object} contentSize Content Size. + * @param {string} xAxis Desired xAxis. + * @param {string} corner Desired corner. + * @param {boolean} stickyBoundaryElement The boundary element to use when + * switching between sticky and normal + * position. + * @param {string} chosenYAxis yAxis to be used. + * @param {Element} boundaryElement Boundary element. + * @param {boolean} forcePosition Don't adjust position based on anchor. * * @return {Object} Popover xAxis position and constraints. */ @@ -28,9 +24,10 @@ export function computePopoverXAxisPosition( contentSize, xAxis, corner, - sticky, + stickyBoundaryElement, chosenYAxis, - boundaryElement + boundaryElement, + forcePosition ) { const { width } = contentSize; const isRTL = document.documentElement.dir === 'rtl'; @@ -91,7 +88,7 @@ export function computePopoverXAxisPosition( let chosenXAxis = xAxis; let contentWidth = null; - if ( ! sticky ) { + if ( ! stickyBoundaryElement && ! forcePosition ) { if ( xAxis === 'center' && centerAlignment.contentWidth === width ) { chosenXAxis = 'center'; } else if ( xAxis === 'left' && leftAlignment.contentWidth === width ) { @@ -110,7 +107,18 @@ export function computePopoverXAxisPosition( chosenXAxis === 'left' ? leftAlignment.contentWidth : rightAlignment.contentWidth; - contentWidth = chosenWidth !== width ? chosenWidth : null; + + // Limit width of the content to the viewport width + if ( width > window.innerWidth ) { + contentWidth = window.innerWidth; + } + + // If we can't find any alignment options that could fit + // our content, then let's fallback to the center of the viewport. + if ( chosenWidth !== width ) { + chosenXAxis = 'center'; + centerAlignment.popoverLeft = window.innerWidth / 2; + } } } @@ -138,16 +146,17 @@ export function computePopoverXAxisPosition( /** * Utility used to compute the popover position over the yAxis * - * @param {Object} anchorRect Anchor Rect. - * @param {Object} contentSize Content Size. - * @param {string} yAxis Desired yAxis. - * @param {string} corner Desired corner. - * @param {boolean} sticky Whether or not to stick the popover to the - * scroll container edge when part of the - * anchor leaves view. - * @param {Element} anchorRef The anchor element. - * @param {Element} relativeOffsetTop If applicable, top offset of the relative - * positioned parent container. + * @param {Object} anchorRect Anchor Rect. + * @param {Object} contentSize Content Size. + * @param {string} yAxis Desired yAxis. + * @param {string} corner Desired corner. + * @param {boolean} stickyBoundaryElement The boundary element to use when + * switching between sticky and normal + * position. + * @param {Element} anchorRef The anchor element. + * @param {Element} relativeOffsetTop If applicable, top offset of the + * relative positioned parent container. + * @param {boolean} forcePosition Don't adjust position based on anchor. * * @return {Object} Popover xAxis position and constraints. */ @@ -156,17 +165,16 @@ export function computePopoverYAxisPosition( contentSize, yAxis, corner, - sticky, + stickyBoundaryElement, anchorRef, - relativeOffsetTop + relativeOffsetTop, + forcePosition ) { const { height } = contentSize; - if ( sticky ) { - const scrollContainerEl = - getScrollContainer( anchorRef ) || document.body; - const scrollRect = scrollContainerEl.getBoundingClientRect(); - const stickyPosition = scrollRect.top + height - relativeOffsetTop; + if ( stickyBoundaryElement ) { + const stickyRect = stickyBoundaryElement.getBoundingClientRect(); + const stickyPosition = stickyRect.top + height - relativeOffsetTop; if ( anchorRect.top <= stickyPosition ) { return { @@ -213,7 +221,7 @@ export function computePopoverYAxisPosition( let chosenYAxis = yAxis; let contentHeight = null; - if ( ! sticky ) { + if ( ! stickyBoundaryElement && ! forcePosition ) { if ( yAxis === 'middle' && middleAlignment.contentHeight === height ) { chosenYAxis = 'middle'; } else if ( yAxis === 'top' && topAlignment.contentHeight === height ) { @@ -256,16 +264,17 @@ export function computePopoverYAxisPosition( * Utility used to compute the popover position and the content max width/height * for a popover given its anchor rect and its content size. * - * @param {Object} anchorRect Anchor Rect. - * @param {Object} contentSize Content Size. - * @param {string} position Position. - * @param {boolean} sticky Whether or not to stick the popover to the - * scroll container edge when part of the - * anchor leaves view. - * @param {Element} anchorRef The anchor element. - * @param {number} relativeOffsetTop If applicable, top offset of the relative - * positioned parent container. - * @param {Element} boundaryElement Boundary element. + * @param {Object} anchorRect Anchor Rect. + * @param {Object} contentSize Content Size. + * @param {string} position Position. + * @param {boolean} stickyBoundaryElement The boundary element to use when + * switching between sticky and normal + * position. + * @param {Element} anchorRef The anchor element. + * @param {number} relativeOffsetTop If applicable, top offset of the + * relative positioned parent container. + * @param {Element} boundaryElement Boundary element. + * @param {boolean} forcePosition Don't adjust position based on anchor. * * @return {Object} Popover position and constraints. */ @@ -273,10 +282,11 @@ export function computePopoverPosition( anchorRect, contentSize, position = 'top', - sticky, + stickyBoundaryElement, anchorRef, relativeOffsetTop, - boundaryElement + boundaryElement, + forcePosition ) { const [ yAxis, xAxis = 'center', corner ] = position.split( ' ' ); @@ -285,18 +295,20 @@ export function computePopoverPosition( contentSize, yAxis, corner, - sticky, + stickyBoundaryElement, anchorRef, - relativeOffsetTop + relativeOffsetTop, + forcePosition ); const xAxisPosition = computePopoverXAxisPosition( anchorRect, contentSize, xAxis, corner, - sticky, + stickyBoundaryElement, yAxisPosition.yAxis, - boundaryElement + boundaryElement, + forcePosition ); return { diff --git a/packages/components/src/range-control/index.js b/packages/components/src/range-control/index.js index 90f55c3c0cd555..543d25accfcfbd 100644 --- a/packages/components/src/range-control/index.js +++ b/packages/components/src/range-control/index.js @@ -99,9 +99,7 @@ function RangeControl( const inputSliderValue = isValueReset ? '' : currentValue; - const rangeFillValue = isValueReset - ? floatClamp( max / 2, min, max ) - : value; + const rangeFillValue = isValueReset ? ( max - min ) / 2 + min : value; const calculatedFillValue = ( ( value - min ) / ( max - min ) ) * 100; const fillValue = isValueReset ? 50 : calculatedFillValue; diff --git a/packages/components/src/range-control/rail.js b/packages/components/src/range-control/rail.js index 93db3351c68121..51c188e8f46493 100644 --- a/packages/components/src/range-control/rail.js +++ b/packages/components/src/range-control/rail.js @@ -64,33 +64,33 @@ function useMarks( { marks, min = 0, max = 100, step = 1, value = 0 } ) { return []; } - const isCustomMarks = Array.isArray( marks ); - - const markCount = Math.round( ( max - min ) / step ); - const marksArray = isCustomMarks - ? marks - : [ ...Array( markCount + 1 ) ].map( ( _, index ) => ( { - value: index, - } ) ); - - const enhancedMarks = marksArray.map( ( mark, index ) => { - const markValue = mark.value !== undefined ? mark.value : value; + const range = max - min; + if ( ! Array.isArray( marks ) ) { + marks = []; + const count = 1 + Math.round( range / step ); + while ( count > marks.push( { value: step * marks.length + min } ) ); + } + const placedMarks = []; + marks.forEach( ( mark, index ) => { + if ( mark.value < min || mark.value > max ) { + return; + } const key = `mark-${ index }`; - const isFilled = markValue * step + min <= value; - const offset = `${ ( markValue / markCount ) * 100 }%`; + const isFilled = mark.value <= value; + const offset = `${ ( ( mark.value - min ) / range ) * 100 }%`; const offsetStyle = { [ isRTL ? 'right' : 'left' ]: offset, }; - return { + placedMarks.push( { ...mark, isFilled, key, style: offsetStyle, - }; + } ); } ); - return enhancedMarks; + return placedMarks; } diff --git a/packages/components/src/range-control/stories/index.js b/packages/components/src/range-control/stories/index.js index d3dca6a1ca1a35..e91fde04b8fbc3 100644 --- a/packages/components/src/range-control/stories/index.js +++ b/packages/components/src/range-control/stories/index.js @@ -75,6 +75,11 @@ const DefaultExample = () => { ); }; +const RangeControlLabeledByMarksType = ( props ) => { + const label = Array.isArray( props.marks ) ? 'Custom' : 'Automatic'; + return <RangeControl { ...{ ...props, label } } />; +}; + export const _default = () => { return <DefaultExample />; }; @@ -131,33 +136,51 @@ export const withReset = () => { return <RangeControlWithState label={ label } allowReset />; }; -export const customMarks = () => { - const marks = [ - { - value: 0, - label: '0', - }, - { - value: 1, - label: '1', - }, - { - value: 2, - label: '2', - }, - { - value: 8, - label: '8', - }, - { - value: 10, - label: '10', - }, +export const marks = () => { + const marksBase = [ + { value: 0, label: '0' }, + { value: 1, label: '1' }, + { value: 2, label: '2' }, + { value: 8, label: '8' }, + { value: 10, label: '10' }, + ]; + const marksWithDecimal = [ + ...marksBase, + { value: 3.5, label: '3.5' }, + { value: 5.8, label: '5.8' }, + ]; + const marksWithNegatives = [ + ...marksBase, + { value: -1, label: '-1' }, + { value: -2, label: '-2' }, + { value: -4, label: '-4' }, + { value: -8, label: '-8' }, ]; + const stepInteger = { min: 0, max: 10, step: 1 }; + const stepDecimal = { min: 0, max: 10, step: 0.1 }; + const minNegative = { min: -10, max: 10, step: 1 }; + const rangeNegative = { min: -10, max: -1, step: 1 }; + + // use a short alias to keep formatting to fewer lines + const Range = RangeControlLabeledByMarksType; return ( <Wrapper> - <RangeControl marks={ marks } min={ 0 } max={ 10 } step={ 1 } /> + <h2>Integer Step</h2> + <Range marks { ...stepInteger } /> + <Range marks={ marksBase } { ...stepInteger } /> + + <h2>Decimal Step</h2> + <Range marks { ...stepDecimal } /> + <Range marks={ marksWithDecimal } { ...stepDecimal } /> + + <h2>Negative Minimum</h2> + <Range marks { ...minNegative } /> + <Range marks={ marksWithNegatives } { ...minNegative } /> + + <h2>Negative Range</h2> + <Range marks { ...rangeNegative } /> + <Range marks={ marksWithNegatives } { ...rangeNegative } /> </Wrapper> ); }; diff --git a/packages/components/src/range-control/styles/range-control-styles.js b/packages/components/src/range-control/styles/range-control-styles.js index 0fa3fbbeb233d9..2b4d0f94633670 100644 --- a/packages/components/src/range-control/styles/range-control-styles.js +++ b/packages/components/src/range-control/styles/range-control-styles.js @@ -11,11 +11,11 @@ import NumberControl from '../../number-control'; import { color, reduceMotion, rtl, space } from '../../utils/style-mixins'; const rangeHeight = () => css( { height: 30, minHeight: 30 } ); +const thumbSize = 20; -export const Root = styled.span` +export const Root = styled.div` -webkit-tap-highlight-color: transparent; box-sizing: border-box; - cursor: pointer; align-items: flex-start; display: inline-flex; justify-content: flex-start; @@ -31,7 +31,7 @@ const wrapperColor = ( { color: colorProp = color( 'ui.borderFocus' ) } ) => { const wrapperMargin = ( { marks } ) => css( { marginBottom: marks ? 16 : null } ); -export const Wrapper = styled.span` +export const Wrapper = styled.div` box-sizing: border-box; color: ${ color( 'blue.medium.focus' ) }; display: block; @@ -115,6 +115,7 @@ export const Track = styled.span` export const MarksWrapper = styled.span` box-sizing: border-box; display: block; + pointer-events: none; position: relative; width: 100%; user-select: none; @@ -166,7 +167,7 @@ export const ThumbWrapper = styled.span` align-items: center; box-sizing: border-box; display: flex; - height: 20px; + height: ${ thumbSize }px; justify-content: center; margin-top: 5px; outline: 0; @@ -174,7 +175,7 @@ export const ThumbWrapper = styled.span` position: absolute; top: 0; user-select: none; - width: 20px; + width: ${ thumbSize }px; ${ rtl( { marginLeft: -10 } ) } `; @@ -202,7 +203,6 @@ export const Thumb = styled.span` box-sizing: border-box; height: 100%; outline: 0; - pointer-events: none; position: absolute; user-select: none; width: 100%; @@ -216,13 +216,13 @@ export const InputRange = styled.input` display: block; height: 100%; left: 0; - margin: 0; + margin: 0 -${ thumbSize / 2 }px; opacity: 0; outline: none; position: absolute; right: 0; top: 0; - width: 100%; + width: calc( 100% + ${ thumbSize }px ); `; const tooltipShow = ( { show } ) => { diff --git a/packages/components/src/sandbox/index.js b/packages/components/src/sandbox/index.js index dd779b984c28ad..0dad29cbe4b565 100644 --- a/packages/components/src/sandbox/index.js +++ b/packages/components/src/sandbox/index.js @@ -1,101 +1,127 @@ /** * WordPress dependencies */ -import { Component, renderToString, createRef } from '@wordpress/element'; -import { withGlobalEvents } from '@wordpress/compose'; +import { + renderToString, + useRef, + useState, + useEffect, +} from '@wordpress/element'; /** * Internal dependencies */ import FocusableIframe from '../focusable-iframe'; -class Sandbox extends Component { - constructor() { - super( ...arguments ); +const observeAndResizeJS = ` + ( function() { + var observer; - this.trySandbox = this.trySandbox.bind( this ); - this.checkMessageForResize = this.checkMessageForResize.bind( this ); + if ( ! window.MutationObserver || ! document.body || ! window.parent ) { + return; + } - this.iframe = createRef(); + function sendResize() { + var clientBoundingRect = document.body.getBoundingClientRect(); - this.state = { - width: 0, - height: 0, - }; - } + window.parent.postMessage( { + action: 'resize', + width: clientBoundingRect.width, + height: clientBoundingRect.height, + }, '*' ); + } - componentDidMount() { - this.trySandbox(); - this.trySandboxWithoutRerender = () => { - this.trySandbox( false ); - }; - // This used to be registered using <iframe onLoad={} />, but it made the iframe blank - // after reordering the containing block. See these two issues for more details: - // https://github.com/WordPress/gutenberg/issues/6146 - // https://github.com/facebook/react/issues/18752 - this.iframe.current.addEventListener( - 'load', - this.trySandboxWithoutRerender, - false - ); - } + observer = new MutationObserver( sendResize ); + observer.observe( document.body, { + attributes: true, + attributeOldValue: false, + characterData: true, + characterDataOldValue: false, + childList: true, + subtree: true + } ); + + window.addEventListener( 'load', sendResize, true ); + + // Hack: Remove viewport unit styles, as these are relative + // the iframe root and interfere with our mechanism for + // determining the unconstrained page bounds. + function removeViewportStyles( ruleOrNode ) { + if( ruleOrNode.style ) { + [ 'width', 'height', 'minHeight', 'maxHeight' ].forEach( function( style ) { + if ( /^\\d+(vmin|vmax|vh|vw)$/.test( ruleOrNode.style[ style ] ) ) { + ruleOrNode.style[ style ] = ''; + } + } ); + } + } - componentDidUpdate( prevProps ) { - const forceRerender = prevProps.html !== this.props.html; + Array.prototype.forEach.call( document.querySelectorAll( '[style]' ), removeViewportStyles ); + Array.prototype.forEach.call( document.styleSheets, function( stylesheet ) { + Array.prototype.forEach.call( stylesheet.cssRules || stylesheet.rules, removeViewportStyles ); + } ); - this.trySandbox( forceRerender ); - } + document.body.style.position = 'absolute'; + document.body.style.width = '100%'; + document.body.setAttribute( 'data-resizable-iframe-connected', '' ); - componentWillUnmount() { - this.iframe.current.removeEventListener( - 'load', - this.trySandboxWithoutRerender - ); - } + sendResize(); - isFrameAccessible() { + // Resize events can change the width of elements with 100% width, but we don't + // get an DOM mutations for that, so do the resize when the window is resized, too. + window.addEventListener( 'resize', sendResize, true ); +} )();`; + +const style = ` + body { + margin: 0; + } + html, + body, + body > div, + body > div iframe { + width: 100%; + } + html.wp-has-aspect-ratio, + body.wp-has-aspect-ratio, + body.wp-has-aspect-ratio > div, + body.wp-has-aspect-ratio > div iframe { + height: 100%; + overflow: hidden; /* If it has an aspect ratio, it shouldn't scroll. */ + } + body > div > * { + margin-top: 0 !important; /* Has to have !important to override inline styles. */ + margin-bottom: 0 !important; + } +`; + +export default function Sandbox( { + html = '', + title = '', + type, + styles = [], + scripts = [], + onFocus, +} ) { + const ref = useRef(); + const [ width, setWidth ] = useState( 0 ); + const [ height, setHeight ] = useState( 0 ); + + function isFrameAccessible() { try { - return !! this.iframe.current.contentDocument.body; + return !! ref.current.contentDocument.body; } catch ( e ) { return false; } } - checkMessageForResize( event ) { - const iframe = this.iframe.current; - - // Attempt to parse the message data as JSON if passed as string - let data = event.data || {}; - if ( 'string' === typeof data ) { - try { - data = JSON.parse( data ); - } catch ( e ) {} - } - - // Verify that the mounted element is the source of the message - if ( ! iframe || iframe.contentWindow !== event.source ) { - return; - } - - // Update the state only if the message is formatted as we expect, i.e. - // as an object with a 'resize' action, width, and height - const { action, width, height } = data; - const { width: oldWidth, height: oldHeight } = this.state; - - if ( - 'resize' === action && - ( oldWidth !== width || oldHeight !== height ) - ) { - this.setState( { width, height } ); - } - } - - trySandbox( forceRerender = false ) { - if ( ! this.isFrameAccessible() ) { + function trySandbox( forceRerender = false ) { + if ( ! isFrameAccessible() ) { return; } - const body = this.iframe.current.contentDocument.body; + const { contentDocument, ownerDocument } = ref.current; + const { body } = contentDocument; if ( ! forceRerender && @@ -104,125 +130,39 @@ class Sandbox extends Component { return; } - const observeAndResizeJS = ` - ( function() { - var observer; - - if ( ! window.MutationObserver || ! document.body || ! window.parent ) { - return; - } - - function sendResize() { - var clientBoundingRect = document.body.getBoundingClientRect(); - - window.parent.postMessage( { - action: 'resize', - width: clientBoundingRect.width, - height: clientBoundingRect.height, - }, '*' ); - } - - observer = new MutationObserver( sendResize ); - observer.observe( document.body, { - attributes: true, - attributeOldValue: false, - characterData: true, - characterDataOldValue: false, - childList: true, - subtree: true - } ); - - window.addEventListener( 'load', sendResize, true ); - - // Hack: Remove viewport unit styles, as these are relative - // the iframe root and interfere with our mechanism for - // determining the unconstrained page bounds. - function removeViewportStyles( ruleOrNode ) { - if( ruleOrNode.style ) { - [ 'width', 'height', 'minHeight', 'maxHeight' ].forEach( function( style ) { - if ( /^\\d+(vmin|vmax|vh|vw)$/.test( ruleOrNode.style[ style ] ) ) { - ruleOrNode.style[ style ] = ''; - } - } ); - } - } - - Array.prototype.forEach.call( document.querySelectorAll( '[style]' ), removeViewportStyles ); - Array.prototype.forEach.call( document.styleSheets, function( stylesheet ) { - Array.prototype.forEach.call( stylesheet.cssRules || stylesheet.rules, removeViewportStyles ); - } ); - - document.body.style.position = 'absolute'; - document.body.style.width = '100%'; - document.body.setAttribute( 'data-resizable-iframe-connected', '' ); - - sendResize(); - - // Resize events can change the width of elements with 100% width, but we don't - // get an DOM mutations for that, so do the resize when the window is resized, too. - window.addEventListener( 'resize', sendResize, true ); - } )();`; - - const style = ` - body { - margin: 0; - } - html, - body, - body > div, - body > div > iframe { - width: 100%; - } - html.wp-has-aspect-ratio, - body.wp-has-aspect-ratio, - body.wp-has-aspect-ratio > div, - body.wp-has-aspect-ratio > div > iframe { - height: 100%; - overflow: hidden; /* If it has an aspect ratio, it shouldn't scroll. */ - } - body > div > * { - margin-top: 0 !important; /* Has to have !important to override inline styles. */ - margin-bottom: 0 !important; - } - `; - // put the html snippet into a html document, and then write it to the iframe's document // we can use this in the future to inject custom styles or scripts. // Scripts go into the body rather than the head, to support embedded content such as Instagram // that expect the scripts to be part of the body. const htmlDoc = ( <html - lang={ document.documentElement.lang } - className={ this.props.type } + lang={ ownerDocument.documentElement.lang } + className={ type } > <head> - <title>{ this.props.title }</title> + <title>{ title }</title> <style dangerouslySetInnerHTML={ { __html: style } } /> - { this.props.styles && - this.props.styles.map( ( rules, i ) => ( - <style - key={ i } - dangerouslySetInnerHTML={ { __html: rules } } - /> - ) ) } + { styles.map( ( rules, i ) => ( + <style + key={ i } + dangerouslySetInnerHTML={ { __html: rules } } + /> + ) ) } </head> <body data-resizable-iframe-connected="data-resizable-iframe-connected" - className={ this.props.type } + className={ type } > - <div - dangerouslySetInnerHTML={ { __html: this.props.html } } - /> + <div dangerouslySetInnerHTML={ { __html: html } } /> <script type="text/javascript" dangerouslySetInnerHTML={ { __html: observeAndResizeJS, } } /> - { this.props.scripts && - this.props.scripts.map( ( src ) => ( - <script key={ src } src={ src } /> - ) ) } + { scripts.map( ( src ) => ( + <script key={ src } src={ src } /> + ) ) } </body> </html> ); @@ -230,38 +170,78 @@ class Sandbox extends Component { // writing the document like this makes it act in the same way as if it was // loaded over the network, so DOM creation and mutation, script execution, etc. // all work as expected - const iframeDocument = this.iframe.current.contentWindow.document; - iframeDocument.open(); - iframeDocument.write( '<!DOCTYPE html>' + renderToString( htmlDoc ) ); - iframeDocument.close(); + contentDocument.open(); + contentDocument.write( '<!DOCTYPE html>' + renderToString( htmlDoc ) ); + contentDocument.close(); } - static get defaultProps() { - return { - html: '', - title: '', - }; - } + useEffect( () => { + trySandbox(); - render() { - const { title, onFocus } = this.props; - - return ( - <FocusableIframe - iframeRef={ this.iframe } - title={ title } - className="components-sandbox" - sandbox="allow-scripts allow-same-origin allow-presentation" - onFocus={ onFocus } - width={ Math.ceil( this.state.width ) } - height={ Math.ceil( this.state.height ) } - /> - ); - } -} + function tryNoForceSandbox() { + trySandbox( false ); + } + + function checkMessageForResize( event ) { + const iframe = ref.current; + + // Verify that the mounted element is the source of the message + if ( ! iframe || iframe.contentWindow !== event.source ) { + return; + } + + // Attempt to parse the message data as JSON if passed as string + let data = event.data || {}; + + if ( 'string' === typeof data ) { + try { + data = JSON.parse( data ); + } catch ( e ) {} + } + + // Update the state only if the message is formatted as we expect, + // i.e. as an object with a 'resize' action. + if ( 'resize' !== data.action ) { + return; + } -Sandbox = withGlobalEvents( { - message: 'checkMessageForResize', -} )( Sandbox ); + setWidth( data.width ); + setHeight( data.height ); + } + + const { ownerDocument } = ref.current; + const { defaultView } = ownerDocument; -export default Sandbox; + // This used to be registered using <iframe onLoad={} />, but it made the iframe blank + // after reordering the containing block. See these two issues for more details: + // https://github.com/WordPress/gutenberg/issues/6146 + // https://github.com/facebook/react/issues/18752 + ref.current.addEventListener( 'load', tryNoForceSandbox, false ); + defaultView.addEventListener( 'message', checkMessageForResize ); + + return () => { + ref.current.removeEventListener( 'load', tryNoForceSandbox, false ); + defaultView.addEventListener( 'message', checkMessageForResize ); + }; + }, [] ); + + useEffect( () => { + trySandbox(); + }, [ title, type, styles, scripts ] ); + + useEffect( () => { + trySandbox( true ); + }, [ html ] ); + + return ( + <FocusableIframe + iframeRef={ ref } + title={ title } + className="components-sandbox" + sandbox="allow-scripts allow-same-origin allow-presentation" + onFocus={ onFocus } + width={ Math.ceil( width ) } + height={ Math.ceil( height ) } + /> + ); +} diff --git a/packages/components/src/select-control/styles/select-control-styles.js b/packages/components/src/select-control/styles/select-control-styles.js index 4d4ab42178c1f3..8a3a5a22500ba9 100644 --- a/packages/components/src/select-control/styles/select-control-styles.js +++ b/packages/components/src/select-control/styles/select-control-styles.js @@ -69,7 +69,6 @@ export const Select = styled.select` color: ${ color( 'black' ) }; display: block; margin: 0; - outline: none; width: 100%; ${ disabledStyles }; diff --git a/packages/components/src/snackbar/index.js b/packages/components/src/snackbar/index.js index 4c5ae0ac7e5b05..84f78aee2ffd9a 100644 --- a/packages/components/src/snackbar/index.js +++ b/packages/components/src/snackbar/index.js @@ -47,19 +47,53 @@ function Snackbar( politeness = 'polite', actions = [], onRemove = noop, + icon = null, + explicitDismiss = false, + // onDismiss is a callback executed when the snackbar is dismissed. + // It is distinct from onRemove, which _looks_ like a callback but is + // actually the function to call to remove the snackbar from the UI. + onDismiss = noop, }, ref ) { + onDismiss = onDismiss || noop; + + function dismissMe( event ) { + if ( event && event.preventDefault ) { + event.preventDefault(); + } + + onDismiss(); + onRemove(); + } + + function onActionClick( event, onClick ) { + event.stopPropagation(); + + onRemove(); + + if ( onClick ) { + onClick( event ); + } + } + useSpokenMessage( spokenMessage, politeness ); + + // Only set up the timeout dismiss if we're not explicitly dismissing. useEffect( () => { const timeoutHandle = setTimeout( () => { - onRemove(); + if ( ! explicitDismiss ) { + onDismiss(); + onRemove(); + } }, NOTICE_TIMEOUT ); return () => clearTimeout( timeoutHandle ); - }, [] ); + }, [ onDismiss, onRemove ] ); - const classes = classnames( className, 'components-snackbar' ); + const classes = classnames( className, 'components-snackbar', { + 'components-snackbar-explicit-dismiss': !! explicitDismiss, + } ); if ( actions && actions.length > 1 ) { // we need to inform developers that snackbar only accepts 1 action warning( @@ -69,17 +103,27 @@ function Snackbar( actions = [ actions[ 0 ] ]; } + const snackbarContentClassnames = classnames( + 'components-snackbar__content', + { + 'components-snackbar__content-with-icon': !! icon, + } + ); + return ( <div ref={ ref } className={ classes } - onClick={ onRemove } + onClick={ ! explicitDismiss ? dismissMe : noop } tabIndex="0" - role="button" - onKeyPress={ onRemove } - aria-label={ __( 'Dismiss this notice' ) } + role={ ! explicitDismiss ? 'button' : '' } + onKeyPress={ ! explicitDismiss ? dismissMe : noop } + aria-label={ ! explicitDismiss ? __( 'Dismiss this notice' ) : '' } > - <div className="components-snackbar__content"> + <div className={ snackbarContentClassnames }> + { icon && ( + <div className="components-snackbar__icon">{ icon }</div> + ) } { children } { actions.map( ( { label, onClick, url }, index ) => { return ( @@ -87,18 +131,27 @@ function Snackbar( key={ index } href={ url } isTertiary - onClick={ ( event ) => { - event.stopPropagation(); - if ( onClick ) { - onClick( event ); - } - } } + onClick={ ( event ) => + onActionClick( event, onClick ) + } className="components-snackbar__action" > { label } </Button> ); } ) } + { explicitDismiss && ( + <span + role="button" + aria-label="Dismiss this notice" + tabIndex="0" + className="components-snackbar__dismiss-button" + onClick={ dismissMe } + onKeyPress={ dismissMe } + > + &#x2715; + </span> + ) } </div> </div> ); diff --git a/packages/components/src/snackbar/stories/index.js b/packages/components/src/snackbar/stories/index.js index dd6a1f30face74..bf634c2131ebc3 100644 --- a/packages/components/src/snackbar/stories/index.js +++ b/packages/components/src/snackbar/stories/index.js @@ -33,3 +33,51 @@ export const withActions = () => { return <Snackbar actions={ actions }>{ content }</Snackbar>; }; + +export const withIcon = () => { + const content = text( + 'Content', + 'Add an icon to make your snackbar stand out' + ); + const icon = text( 'Icon (as unicode emoji)', '🌮' ); + + return ( + <Snackbar + icon={ + <span role="img" aria-label="Icon" style={ { fontSize: 21 } }> + { icon } + </span> + } + > + { content } + </Snackbar> + ); +}; + +export const withExplicitDismiss = () => { + const content = text( + 'Content', + 'Add a cross to explicitly close the snackbar, and do not hide it automatically' + ); + + return <Snackbar explicitDismiss={ true }>{ content }</Snackbar>; +}; + +export const withActionAndExpicitDismiss = () => { + const content = text( + 'Content', + 'Add an action and a cross to explicitly close the snackbar, and do not hide it automatically' + ); + const actions = [ + { + label: text( 'Label', 'Open WP.org' ), + url: text( 'URL', 'https://wordpress.org' ), + }, + ]; + + return ( + <Snackbar actions={ actions } explicitDismiss={ true }> + { content } + </Snackbar> + ); +}; diff --git a/packages/components/src/snackbar/style.scss b/packages/components/src/snackbar/style.scss index 5d82bc97ac9084..bfc5c9b7480ace 100644 --- a/packages/components/src/snackbar/style.scss +++ b/packages/components/src/snackbar/style.scss @@ -20,6 +20,25 @@ 0 0 0 1px $white, 0 0 0 3px var(--wp-admin-theme-color); } + + &.components-snackbar-explicit-dismiss { + cursor: default; + } + + .components-snackbar__content-with-icon { + margin-left: 24px; + } + + .components-snackbar__icon { + position: absolute; + top: 24px; + left: 28px; + } + + .components-snackbar__dismiss-button { + margin-left: 32px; + cursor: pointer; + } } .components-snackbar__action.components-button { diff --git a/packages/components/src/spinner/index.js b/packages/components/src/spinner/index.js index cec2053ab27e14..0491e08903069d 100644 --- a/packages/components/src/spinner/index.js +++ b/packages/components/src/spinner/index.js @@ -1,3 +1,8 @@ +/** + * Internal dependencies + */ +import { StyledSpinner } from './styles/spinner-styles'; + export default function Spinner() { - return <span className="components-spinner" />; + return <StyledSpinner className="components-spinner" />; } diff --git a/packages/components/src/spinner/style.scss b/packages/components/src/spinner/style.scss deleted file mode 100644 index b674920ed84856..00000000000000 --- a/packages/components/src/spinner/style.scss +++ /dev/null @@ -1,34 +0,0 @@ -.components-spinner { - display: inline-block; - background-color: $gray-600; - width: $spinner-size; - height: $spinner-size; - opacity: 0.7; - margin: 5px 11px 0; - border-radius: 100%; - position: relative; - - &::before { - /* rtl:begin:ignore */ - content: ""; - position: absolute; - background-color: $white; - top: ( $spinner-size - ( $spinner-size * ( 2 / 3 ) ) ) / 2; - left: ( $spinner-size - ( $spinner-size * ( 2 / 3 ) ) ) / 2; - width: ( $spinner-size / 4.5 ); - height: ( $spinner-size / 4.5 ); - border-radius: 100%; - transform-origin: ( $spinner-size / 3 ) ( $spinner-size / 3 ); - animation: components-spinner__animation 1s infinite linear; - /* rtl:end:ignore */ - } -} - -@keyframes components-spinner__animation { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/packages/components/src/spinner/styles/spinner-styles.js b/packages/components/src/spinner/styles/spinner-styles.js new file mode 100644 index 00000000000000..8f30a9febfbd28 --- /dev/null +++ b/packages/components/src/spinner/styles/spinner-styles.js @@ -0,0 +1,49 @@ +/** + * External dependencies + */ +import styled from '@emotion/styled'; +import { keyframes } from '@emotion/core'; + +/** + * Internal dependencies + */ +import { color, config } from '../../utils'; + +const spinAnimation = keyframes` + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +`; + +const topLeft = `calc( ( ${ config( 'spinnerSize' ) } - ${ config( + 'spinnerSize' +) } * ( 2 / 3 ) ) / 2 )`; + +export const StyledSpinner = styled.span` + display: inline-block; + background-color: ${ color( 'gray.600' ) }; + width: ${ config( 'spinnerSize' ) }; + height: ${ config( 'spinnerSize' ) }; + opacity: 0.7; + margin: 5px 11px 0; + border-radius: 100%; + position: relative; + + &::before { + content: ''; + position: absolute; + background-color: ${ color( 'white' ) }; + top: ${ topLeft }; + left: ${ topLeft }; + width: calc( ${ config( 'spinnerSize' ) } / 4.5 ); + height: calc( ${ config( 'spinnerSize' ) } / 4.5 ); + border-radius: 100%; + transform-origin: calc( ${ config( 'spinnerSize' ) } / 3 ) + calc( ${ config( 'spinnerSize' ) } / 3 ); + animation: ${ spinAnimation } 1s infinite linear; + } +`; diff --git a/packages/components/src/style.scss b/packages/components/src/style.scss index 8df959bfb4a428..a7b92c1192b13d 100644 --- a/packages/components/src/style.scss +++ b/packages/components/src/style.scss @@ -12,7 +12,6 @@ @import "./custom-select-control/style.scss"; @import "./date-time/style.scss"; @import "./dimension-control/style.scss"; -@import "./disabled/style.scss"; @import "./draggable/style.scss"; @import "./drop-zone/style.scss"; @import "./dropdown/style.scss"; @@ -37,7 +36,6 @@ @import "./scroll-lock/style.scss"; @import "./select-control/style.scss"; @import "./snackbar/style.scss"; -@import "./spinner/style.scss"; @import "./tab-panel/style.scss"; @import "./text-control/style.scss"; @import "./tip/style.scss"; diff --git a/packages/components/src/text-control/index.js b/packages/components/src/text-control/index.js index a172eed483e50a..ba1654ddd511e5 100644 --- a/packages/components/src/text-control/index.js +++ b/packages/components/src/text-control/index.js @@ -2,25 +2,50 @@ * WordPress dependencies */ import { useInstanceId } from '@wordpress/compose'; +import { forwardRef } from '@wordpress/element'; /** * Internal dependencies */ import BaseControl from '../base-control'; -export default function TextControl( { - label, - hideLabelFromVision, - value, - help, - className, - onChange, - type = 'text', - ...props -} ) { +/** + * @typedef OwnProps + * @property {string} label Label for the control. + * @property {boolean} [hideLabelFromVision] Whether to accessibly hide the label. + * @property {string} value Value of the input. + * @property {string} [help] Optional help text for the control. + * @property {string} [className] Classname passed to BaseControl wrapper + * @property {(value: string) => void} onChange Handle changes. + * @property {string} [type='text'] Type of the input. + */ + +/** @typedef {OwnProps & import('react').ComponentProps<'input'>} Props */ + +/** + * + * @param {Props} props Props + * @param {import('react').Ref<HTMLInputElement>} [ref] + */ +function TextControl( + { + label, + hideLabelFromVision, + value, + help, + className, + onChange, + type = 'text', + ...props + }, + ref +) { const instanceId = useInstanceId( TextControl ); const id = `inspector-text-control-${ instanceId }`; - const onChangeValue = ( event ) => onChange( event.target.value ); + const onChangeValue = ( + /** @type {import('react').ChangeEvent<HTMLInputElement>} */ + event + ) => onChange( event.target.value ); return ( <BaseControl @@ -37,8 +62,11 @@ export default function TextControl( { value={ value } onChange={ onChangeValue } aria-describedby={ !! help ? id + '__help' : undefined } + ref={ ref } { ...props } /> </BaseControl> ); } + +export default forwardRef( TextControl ); diff --git a/packages/components/src/tip/index.js b/packages/components/src/tip/index.js index ca9b05cd2c3df9..877d19b1ef044f 100644 --- a/packages/components/src/tip/index.js +++ b/packages/components/src/tip/index.js @@ -1,8 +1,17 @@ /** - * Internal dependencies + * WordPress dependencies */ -import { SVG, Path } from '../'; +import { SVG, Path } from '@wordpress/primitives'; +/** + * @typedef Props + * @property {import('react').ReactNode} children Children to render in the tip. + */ + +/** + * @param {Props} props + * @return {JSX.Element} Element + */ function Tip( props ) { return ( <div className="components-tip"> diff --git a/packages/components/src/toggle-control/README.md b/packages/components/src/toggle-control/README.md index fcc85df0af8b87..1413733c4628d9 100644 --- a/packages/components/src/toggle-control/README.md +++ b/packages/components/src/toggle-control/README.md @@ -53,7 +53,7 @@ If no value is passed the toggle will be unchecked. A function that receives the checked state (boolean) as input. - Type: `function` -- Required: Yes +- Required: No ### className diff --git a/packages/components/src/tooltip/index.js b/packages/components/src/tooltip/index.js index 28508d4d483d28..adcebdab9dcd9c 100644 --- a/packages/components/src/tooltip/index.js +++ b/packages/components/src/tooltip/index.js @@ -1,16 +1,17 @@ /** * External dependencies */ -import { debounce, includes } from 'lodash'; +import { includes } from 'lodash'; /** * WordPress dependencies */ import { - Component, Children, cloneElement, concatChildren, + useEffect, + useState, } from '@wordpress/element'; /** @@ -18,66 +19,72 @@ import { */ import Popover from '../popover'; import Shortcut from '../shortcut'; +import { useDebounce } from '@wordpress/compose'; /** * Time over children to wait before showing tooltip * * @type {number} */ -const TOOLTIP_DELAY = 700; - -class Tooltip extends Component { - constructor() { - super( ...arguments ); - - this.delayedSetIsOver = debounce( - ( isOver ) => this.setState( { isOver } ), - TOOLTIP_DELAY - ); - - /** - * Prebound `isInMouseDown` handler, created as a constant reference to - * assure ability to remove in component unmount. - * - * @type {Function} - */ - this.cancelIsMouseDown = this.createSetIsMouseDown( false ); - - /** - * Whether a the mouse is currently pressed, used in determining whether - * to handle a focus event as displaying the tooltip immediately. - * - * @type {boolean} - */ - this.isInMouseDown = false; - - this.state = { - isOver: false, - }; - } +export const TOOLTIP_DELAY = 700; - componentWillUnmount() { - this.delayedSetIsOver.cancel(); +const emitToChild = ( children, eventName, event ) => { + if ( Children.count( children ) !== 1 ) { + return; + } - document.removeEventListener( 'mouseup', this.cancelIsMouseDown ); + const child = Children.only( children ); + if ( typeof child.props[ eventName ] === 'function' ) { + child.props[ eventName ]( event ); } +}; - emitToChild( eventName, event ) { - const { children } = this.props; - if ( Children.count( children ) !== 1 ) { - return; - } +function Tooltip( { children, position, text, shortcut } ) { + /** + * Whether a mouse is currently pressed, used in determining whether + * to handle a focus event as displaying the tooltip immediately. + * + * @type {boolean} + */ + const [ isMouseDown, setIsMouseDown ] = useState( false ); + const [ isOver, setIsOver ] = useState( false ); + const delayedSetIsOver = useDebounce( setIsOver, TOOLTIP_DELAY ); + + const createMouseDown = ( event ) => { + // Preserve original child callback behavior + emitToChild( children, 'onMouseDown', event ); + + // On mouse down, the next `mouseup` should revert the value of the + // instance property and remove its own event handler. The bind is + // made on the document since the `mouseup` might not occur within + // the bounds of the element. + document.addEventListener( 'mouseup', cancelIsMouseDown ); + setIsMouseDown( true ); + }; + + const createMouseUp = ( event ) => { + emitToChild( children, 'onMouseUp', event ); + document.removeEventListener( 'mouseup', cancelIsMouseDown ); + setIsMouseDown( false ); + }; + + const createMouseEvent = ( type ) => { + if ( type === 'mouseUp' ) return createMouseUp; + if ( type === 'mouseDown' ) return createMouseDown; + }; - const child = Children.only( children ); - if ( typeof child.props[ eventName ] === 'function' ) { - child.props[ eventName ]( event ); - } - } + /** + * Prebound `isInMouseDown` handler, created as a constant reference to + * assure ability to remove in component unmount. + * + * @type {Function} + */ + const cancelIsMouseDown = createMouseEvent( 'mouseUp' ); - createToggleIsOver( eventName, isDelayed ) { + const createToggleIsOver = ( eventName, isDelayed ) => { return ( event ) => { // Preserve original child callback behavior - this.emitToChild( eventName, event ); + emitToChild( children, eventName, event ); // Mouse events behave unreliably in React for disabled elements, // firing on mouseenter but not mouseleave. Further, the default @@ -92,99 +99,71 @@ class Tooltip extends Component { // A focus event will occur as a result of a mouse click, but it // should be disambiguated between interacting with the button and // using an explicit focus shift as a cue to display the tooltip. - if ( 'focus' === event.type && this.isInMouseDown ) { + if ( 'focus' === event.type && isMouseDown ) { return; } // Needed in case unsetting is over while delayed set pending, i.e. // quickly blur/mouseleave before delayedSetIsOver is called - this.delayedSetIsOver.cancel(); + delayedSetIsOver.cancel(); - const isOver = includes( [ 'focus', 'mouseenter' ], event.type ); - if ( isOver === this.state.isOver ) { + const _isOver = includes( [ 'focus', 'mouseenter' ], event.type ); + if ( _isOver === isOver ) { return; } if ( isDelayed ) { - this.delayedSetIsOver( isOver ); + delayedSetIsOver( _isOver ); } else { - this.setState( { isOver } ); + setIsOver( _isOver ); } }; - } - - /** - * Creates an event callback to handle assignment of the `isInMouseDown` - * instance property in response to a `mousedown` or `mouseup` event. - * - * @param {boolean} isMouseDown Whether handler is to be created for the - * `mousedown` event, as opposed to `mouseup`. - * - * @return {Function} Event callback handler. - */ - createSetIsMouseDown( isMouseDown ) { - return ( event ) => { - // Preserve original child callback behavior - this.emitToChild( - isMouseDown ? 'onMouseDown' : 'onMouseUp', - event + }; + const clearOnUnmount = () => { + delayedSetIsOver.cancel(); + }; + + useEffect( () => clearOnUnmount, [] ); + + if ( Children.count( children ) !== 1 ) { + if ( 'development' === process.env.NODE_ENV ) { + // eslint-disable-next-line no-console + console.error( + 'Tooltip should be called with only a single child element.' ); - - // On mouse down, the next `mouseup` should revert the value of the - // instance property and remove its own event handler. The bind is - // made on the document since the `mouseup` might not occur within - // the bounds of the element. - document[ - isMouseDown ? 'addEventListener' : 'removeEventListener' - ]( 'mouseup', this.cancelIsMouseDown ); - - this.isInMouseDown = isMouseDown; - }; - } - - render() { - const { children, position, text, shortcut } = this.props; - if ( Children.count( children ) !== 1 ) { - if ( 'development' === process.env.NODE_ENV ) { - // eslint-disable-next-line no-console - console.error( - 'Tooltip should be called with only a single child element.' - ); - } - - return children; } - const child = Children.only( children ); - const { isOver } = this.state; - return cloneElement( child, { - onMouseEnter: this.createToggleIsOver( 'onMouseEnter', true ), - onMouseLeave: this.createToggleIsOver( 'onMouseLeave' ), - onClick: this.createToggleIsOver( 'onClick' ), - onFocus: this.createToggleIsOver( 'onFocus' ), - onBlur: this.createToggleIsOver( 'onBlur' ), - onMouseDown: this.createSetIsMouseDown( true ), - children: concatChildren( - child.props.children, - isOver && ( - <Popover - focusOnMount={ false } - position={ position } - className="components-tooltip" - aria-hidden="true" - animate={ false } - noArrow={ true } - > - { text } - <Shortcut - className="components-tooltip__shortcut" - shortcut={ shortcut } - /> - </Popover> - ) - ), - } ); + return children; } + + const child = Children.only( children ); + return cloneElement( child, { + onMouseEnter: createToggleIsOver( 'onMouseEnter', true ), + onMouseLeave: createToggleIsOver( 'onMouseLeave' ), + onClick: createToggleIsOver( 'onClick' ), + onFocus: createToggleIsOver( 'onFocus' ), + onBlur: createToggleIsOver( 'onBlur' ), + onMouseDown: createMouseEvent( 'mouseDown' ), + children: concatChildren( + child.props.children, + isOver && ( + <Popover + focusOnMount={ false } + position={ position } + className="components-tooltip" + aria-hidden="true" + animate={ false } + noArrow={ true } + > + { text } + <Shortcut + className="components-tooltip__shortcut" + shortcut={ shortcut } + /> + </Popover> + ) + ), + } ); } export default Tooltip; diff --git a/packages/components/src/tooltip/test/index.js b/packages/components/src/tooltip/test/index.js index 1bfc61b484ddb5..919dc70e3b9b30 100644 --- a/packages/components/src/tooltip/test/index.js +++ b/packages/components/src/tooltip/test/index.js @@ -2,13 +2,16 @@ * External dependencies */ import { shallow, mount } from 'enzyme'; -import TestUtils from 'react-dom/test-utils'; -import ReactDOM from 'react-dom'; /** * Internal dependencies */ import Tooltip from '../'; +/** + * WordPress dependencies + */ +import { TOOLTIP_DELAY } from '../index.js'; +import { act } from '@testing-library/react'; describe( 'Tooltip', () => { describe( '#render()', () => { @@ -44,7 +47,8 @@ describe( 'Tooltip', () => { </Tooltip> ); - wrapper.setState( { isOver: true } ); + const event = { type: 'focus', currentTarget: {} }; + wrapper.simulate( 'focus', event ); const button = wrapper.find( 'button' ); const popover = wrapper.find( 'Popover' ); @@ -76,11 +80,10 @@ describe( 'Tooltip', () => { const popover = wrapper.find( 'Popover' ); expect( originalFocus ).toHaveBeenCalledWith( event ); - expect( wrapper.state( 'isOver' ) ).toBe( true ); expect( popover ).toHaveLength( 1 ); } ); - it( 'should show not popover on focus as result of mousedown', () => { + it( 'should show not popover on focus as result of mousedown', async () => { const originalOnMouseDown = jest.fn(); const originalOnMouseUp = jest.fn(); const wrapper = mount( @@ -110,11 +113,10 @@ describe( 'Tooltip', () => { button.simulate( event.type, event ); const popover = wrapper.find( 'Popover' ); - expect( wrapper.state( 'isOver' ) ).toBe( false ); expect( popover ).toHaveLength( 0 ); event = new window.MouseEvent( 'mouseup' ); - document.dispatchEvent( event ); + await act( async () => document.dispatchEvent( event ) ); expect( originalOnMouseUp ).toHaveBeenCalledWith( expect.objectContaining( { type: event.type, @@ -124,7 +126,8 @@ describe( 'Tooltip', () => { it( 'should show popover on delayed mouseenter', () => { const originalMouseEnter = jest.fn(); - const wrapper = TestUtils.renderIntoDocument( + jest.useFakeTimers(); + const wrapper = mount( <Tooltip text="Help text"> <button onMouseEnter={ originalMouseEnter } @@ -135,32 +138,18 @@ describe( 'Tooltip', () => { </Tooltip> ); - const button = TestUtils.findRenderedDOMComponentWithTag( - wrapper, - 'button' - ); - // eslint-disable-next-line react/no-find-dom-node - TestUtils.Simulate.mouseEnter( ReactDOM.findDOMNode( button ) ); + const button = wrapper.find( 'button' ); + button.simulate( 'mouseenter', { type: 'mouseenter' } ); + const popoverBeforeTimeout = wrapper.find( 'Popover' ); + expect( popoverBeforeTimeout ).toHaveLength( 0 ); expect( originalMouseEnter ).toHaveBeenCalledTimes( 1 ); - expect( wrapper.state.isOver ).toBe( false ); - expect( - TestUtils.scryRenderedDOMComponentsWithClass( - wrapper, - 'components-popover' - ) - ).toHaveLength( 0 ); // Force delayedSetIsOver to be called - wrapper.delayedSetIsOver.flush(); - - expect( wrapper.state.isOver ).toBe( true ); - expect( - TestUtils.scryRenderedDOMComponentsWithClass( - wrapper, - 'components-popover' - ) - ).toHaveLength( 1 ); + setTimeout( () => { + const popoverAfterTimeout = wrapper.find( 'Popover' ); + expect( popoverAfterTimeout ).toHaveLength( 1 ); + }, TOOLTIP_DELAY ); } ); it( 'should ignore mouseenter on disabled elements', () => { @@ -189,8 +178,6 @@ describe( 'Tooltip', () => { expect( originalMouseEnter ).not.toHaveBeenCalled(); const popover = wrapper.find( 'Popover' ); - wrapper.instance().delayedSetIsOver.flush(); - expect( wrapper.state( 'isOver' ) ).toBe( false ); expect( popover ).toHaveLength( 0 ); } ); @@ -211,13 +198,11 @@ describe( 'Tooltip', () => { const button = wrapper.find( 'button' ); button.simulate( 'mouseenter' ); - button.simulate( 'mouseleave' ); - - wrapper.instance().delayedSetIsOver.flush(); - const popover = wrapper.find( 'Popover' ); - expect( wrapper.state( 'isOver' ) ).toBe( false ); - expect( popover ).toHaveLength( 0 ); + setTimeout( () => { + const popover = wrapper.find( 'Popover' ); + expect( popover ).toHaveLength( 0 ); + }, TOOLTIP_DELAY ); } ); } ); } ); diff --git a/packages/components/src/unit-control/index.native.js b/packages/components/src/unit-control/index.native.js new file mode 100644 index 00000000000000..93355f7a34df09 --- /dev/null +++ b/packages/components/src/unit-control/index.native.js @@ -0,0 +1,144 @@ +/** + * External dependencies + */ +import { + Text, + View, + TouchableWithoutFeedback, + Platform, + findNodeHandle, +} from 'react-native'; + +/** + * Internal dependencies + */ +import RangeCell from '../mobile/bottom-sheet/range-cell'; +import StepperCell from '../mobile/bottom-sheet/stepper-cell'; +import Picker from '../mobile/picker'; +import styles from './style.scss'; +import { CSS_UNITS } from './utils'; +/** + * WordPress dependencies + */ +import { useRef } from '@wordpress/element'; +import { withPreferredColorScheme } from '@wordpress/compose'; +import { __, sprintf } from '@wordpress/i18n'; + +function UnitControl( { + currentInput, + label, + value, + onChange, + onUnitChange, + initialPosition, + min, + max, + separatorType, + units = CSS_UNITS, + unit, + getStylesFromColorScheme, + decimalNum, + ...props +} ) { + const pickerRef = useRef(); + const anchorNodeRef = useRef(); + + function onPickerPresent() { + if ( pickerRef?.current ) { + pickerRef.current.presentPicker(); + } + } + + const currentInputValue = currentInput === null ? value : currentInput; + const initialControlValue = isFinite( currentInputValue ) + ? currentInputValue + : initialPosition; + + const unitButtonTextStyle = getStylesFromColorScheme( + styles.unitButtonText, + styles.unitButtonTextDark + ); + + const renderUnitButton = () => { + const accessibilityHint = + Platform.OS === 'ios' + ? __( 'Double tap to open Action Sheet with available options' ) + : __( + 'Double tap to open Bottom Sheet with available options' + ); + + /* translators: accessibility text. Inform about current unit value. %s: Current unit value. */ + const accessibilityLabel = sprintf( __( 'Current unit is %s' ), unit ); + + return ( + <TouchableWithoutFeedback + onPress={ onPickerPresent } + accessibilityLabel={ accessibilityLabel } + accessibilityRole="button" + accessibilityHint={ accessibilityHint } + > + <View style={ styles.unitButton }> + <Text style={ unitButtonTextStyle }>{ unit }</Text> + </View> + </TouchableWithoutFeedback> + ); + }; + + const getAnchor = () => + anchorNodeRef?.current + ? findNodeHandle( anchorNodeRef?.current ) + : undefined; + + const renderUnitPicker = () => { + return ( + <View style={ styles.unitMenu } ref={ anchorNodeRef }> + { renderUnitButton() } + <Picker + ref={ pickerRef } + options={ units } + onChange={ onUnitChange } + hideCancelButton + leftAlign + getAnchor={ getAnchor } + /> + </View> + ); + }; + + return ( + <> + { unit !== '%' ? ( + <StepperCell + label={ label } + max={ max } + min={ min } + onChange={ onChange } + separatorType={ separatorType } + value={ value } + defaultValue={ initialControlValue } + shouldDisplayTextInput + decimalNum={ unit === 'px' ? 0 : decimalNum } + { ...props } + > + { renderUnitPicker() } + </StepperCell> + ) : ( + <RangeCell + label={ label } + onChange={ onChange } + minimumValue={ min } + maximumValue={ max } + value={ value } + defaultValue={ initialControlValue } + separatorType={ separatorType } + decimalNum={ decimalNum } + { ...props } + > + { renderUnitPicker() } + </RangeCell> + ) } + </> + ); +} + +export default withPreferredColorScheme( UnitControl ); diff --git a/packages/components/src/unit-control/style.native.scss b/packages/components/src/unit-control/style.native.scss new file mode 100644 index 00000000000000..1ad7b3109c2d59 --- /dev/null +++ b/packages/components/src/unit-control/style.native.scss @@ -0,0 +1,19 @@ +.unitButtonText { + color: $blue-wordpress; +} + +.unitButtonTextDark { + color: $blue-30; +} + +.unitButton { + padding-right: $grid-unit; + padding-left: $grid-unit; + align-items: flex-end; + justify-content: flex-end; +} + +.unitMenu { + justify-content: center; + align-items: center; +} diff --git a/packages/components/src/utils/colors-values.js b/packages/components/src/utils/colors-values.js index b9e2313c6bbb44..c22d2ece813978 100644 --- a/packages/components/src/utils/colors-values.js +++ b/packages/components/src/utils/colors-values.js @@ -169,6 +169,7 @@ export const COLORS = { darkOpacity: DARK_OPACITY, darkOpacityLight: DARK_OPACITY_LIGHT, mediumGray: G2.mediumGray, + gray: G2.gray, lightGray: merge( {}, LIGHT_GRAY, G2.lightGray ), lightGrayLight: LIGHT_OPACITY_LIGHT, blue: merge( {}, BLUE, G2.blue ), diff --git a/packages/components/src/utils/colors.js b/packages/components/src/utils/colors.js index 43518ce29de140..6815964f57a879 100644 --- a/packages/components/src/utils/colors.js +++ b/packages/components/src/utils/colors.js @@ -28,7 +28,7 @@ export function rgba( hexValue = '', alpha = 1 ) { /** * Retrieves a color from the color palette. * - * @param {string} value The value to retrieve. + * @param {import('lodash').PropertyPath} value The value to retrieve. * @return {string} The color (or fallback, if not found). * * @example diff --git a/packages/components/src/utils/config-values.js b/packages/components/src/utils/config-values.js index 8cdad985401542..21311424087ae7 100644 --- a/packages/components/src/utils/config-values.js +++ b/packages/components/src/utils/config-values.js @@ -3,4 +3,5 @@ export default { borderWidth: '1px', borderWidthFocus: '1.5px', borderWidthTab: '4px', + spinnerSize: '18px', }; diff --git a/packages/components/src/utils/hooks/use-controlled-state.js b/packages/components/src/utils/hooks/use-controlled-state.js index f07c916d7f1c23..774e5b15863c0f 100644 --- a/packages/components/src/utils/hooks/use-controlled-state.js +++ b/packages/components/src/utils/hooks/use-controlled-state.js @@ -8,6 +8,14 @@ import { useEffect, useState } from '@wordpress/element'; */ import { isValueDefined, getDefinedValue } from '../values'; +/** + * @template T + * @typedef Options + * @property {T | undefined} initial Initial value + * @property {T | ""} fallback Fallback value + */ + +/** @type {Readonly<{ initial: undefined, fallback: '' }>} */ const defaultOptions = { initial: undefined, /** @@ -33,12 +41,12 @@ const defaultOptions = { * Unlike the basic useState hook, useControlledState's state can * be updated if a new incoming prop value is changed. * - * @param {any} currentState The current value. - * @param {Object} options Additional options for the hook. - * @param {any} options.initial The initial state. - * @param {any} options.fallback The state to use when no state is defined. + * @template T + * + * @param {T | undefined} currentState The current value. + * @param {Options<T>} [options=defaultOptions] Additional options for the hook. * - * @return {[*, Function]} The controlled value and the value setter. + * @return {[T | "", (nextState: T) => void]} The controlled value and the value setter. */ function useControlledState( currentState, options = defaultOptions ) { const { initial, fallback } = { ...defaultOptions, ...options }; @@ -60,11 +68,14 @@ function useControlledState( currentState, options = defaultOptions ) { fallback ); + /* eslint-disable jsdoc/no-undefined-types */ + /** @type {(nextState: T) => void} */ const setState = ( nextState ) => { if ( ! hasCurrentState ) { setInternalState( nextState ); } }; + /* eslint-enable jsdoc/no-undefined-types */ return [ state, setState ]; } diff --git a/packages/components/src/utils/hooks/use-jump-step.js b/packages/components/src/utils/hooks/use-jump-step.js index f3ac123f6c8cfb..d241bfcdee5a4a 100644 --- a/packages/components/src/utils/hooks/use-jump-step.js +++ b/packages/components/src/utils/hooks/use-jump-step.js @@ -29,6 +29,7 @@ function useJumpStep( { const [ isShiftKey, setIsShiftKey ] = useState( false ); useEffect( () => { + /** @type {(event: KeyboardEvent)=>void} */ const handleShiftKeyToggle = ( event ) => { setIsShiftKey( event.shiftKey ); }; diff --git a/packages/components/src/utils/hooks/use-update-effect.js b/packages/components/src/utils/hooks/use-update-effect.js index fa13e4f9d0d65b..724be8746ba272 100644 --- a/packages/components/src/utils/hooks/use-update-effect.js +++ b/packages/components/src/utils/hooks/use-update-effect.js @@ -3,10 +3,13 @@ */ import { useRef, useEffect } from '@wordpress/element'; -/* +/** * A `React.useEffect` that will not run on the first render. * Source: * https://github.com/reakit/reakit/blob/master/packages/reakit-utils/src/useUpdateEffect.ts + * + * @param {import('react').EffectCallback} effect + * @param {import('react').DependencyList} deps */ function useUpdateEffect( effect, deps ) { const mounted = useRef( false ); diff --git a/packages/components/src/utils/math.js b/packages/components/src/utils/math.js index dad8c508a21671..662010f0c1900d 100644 --- a/packages/components/src/utils/math.js +++ b/packages/components/src/utils/math.js @@ -6,7 +6,7 @@ import { clamp } from 'lodash'; /** * Parses and retrieves a number value. * - * @param {any} value The incoming value. + * @param {unknown} value The incoming value. * * @return {number} The parsed number value. */ @@ -19,26 +19,34 @@ export function getNumber( value ) { /** * Safely adds 2 values. * - * @param {number|string} args Values to add together. + * @param {Array<number|string>} args Values to add together. * * @return {number} The sum of values. */ export function add( ...args ) { - return args.reduce( ( sum, arg ) => sum + getNumber( arg ), 0 ); + return args.reduce( + /** @type {(sum:number, arg: number|string) => number} */ + ( sum, arg ) => sum + getNumber( arg ), + 0 + ); } /** * Safely subtracts 2 values. * - * @param {number|string} args Values to subtract together. + * @param {Array<number|string>} args Values to subtract together. * - * @return {number} The difference of the 2 values. + * @return {number} The difference of the values. */ export function subtract( ...args ) { - return args.reduce( ( diff, arg, index ) => { - const value = getNumber( arg ); - return index === 0 ? value : diff - value; - } ); + return args.reduce( + /** @type {(diff:number, arg: number|string, index:number) => number} */ + ( diff, arg, index ) => { + const value = getNumber( arg ); + return index === 0 ? value : diff - value; + }, + 0 + ); } /** @@ -84,12 +92,7 @@ export function roundClamp( * Clamps a value based on a min/max range with rounding. * Returns a string. * - * @param {any} args Arguments for roundClamp(). - * @property {number} value The value. - * @property {number} min The minimum range. - * @property {number} max The maximum range. - * @property {number} step A multiplier for the value. - * + * @param {Parameters<typeof roundClamp>} args Arguments for roundClamp(). * @return {string} The rounded and clamped value. */ export function roundClampString( ...args ) { diff --git a/packages/components/src/utils/rtl.js b/packages/components/src/utils/rtl.js index 88a3f651af96c1..89fefa7508b65c 100644 --- a/packages/components/src/utils/rtl.js +++ b/packages/components/src/utils/rtl.js @@ -65,9 +65,9 @@ function getConvertedKey( key ) { /** * An incredibly basic ltr -> rtl converter for style properties * - * @param {Object} ltrStyles + * @param {import('react').CSSProperties} ltrStyles * - * @return {Object} Converted ltr -> rtl styles + * @return {import('react').CSSProperties} Converted ltr -> rtl styles */ export const convertLTRToRTL = ( ltrStyles = {} ) => { return mapKeys( ltrStyles, ( _value, key ) => getConvertedKey( key ) ); @@ -76,8 +76,8 @@ export const convertLTRToRTL = ( ltrStyles = {} ) => { /** * A higher-order function that create an incredibly basic ltr -> rtl style converter for CSS objects. * - * @param {Object} ltrStyles Ltr styles. Converts and renders from ltr -> rtl styles, if applicable. - * @param {null|Object} rtlStyles Rtl styles. Renders if provided. + * @param {import('react').CSSProperties} ltrStyles Ltr styles. Converts and renders from ltr -> rtl styles, if applicable. + * @param {import('react').CSSProperties} [rtlStyles] Rtl styles. Renders if provided. * * @return {Function} A function to output CSS styles for Emotion's renderer */ @@ -86,9 +86,11 @@ export function rtl( ltrStyles = {}, rtlStyles ) { const isRTL = getRTL(); if ( rtlStyles ) { + // @ts-ignore: `css` types are wrong, it can accept an object: https://emotion.sh/docs/object-styles#with-css return isRTL ? css( rtlStyles ) : css( ltrStyles ); } + // @ts-ignore: `css` types are wrong, it can accept an object: https://emotion.sh/docs/object-styles#with-css return isRTL ? css( convertLTRToRTL( ltrStyles ) ) : css( ltrStyles ); }; } diff --git a/packages/components/src/utils/space.js b/packages/components/src/utils/space.js index ecf7a64f069bae..cfe8d2f0488d59 100644 --- a/packages/components/src/utils/space.js +++ b/packages/components/src/utils/space.js @@ -3,7 +3,7 @@ const SPACE_GRID_BASE = 8; /** * Creates a spacing CSS value (px) based on grid system values. * - * @param {number} value Multiplier against the grid base value (8) + * @param {number} [value=1] Multiplier against the grid base value (8) * @return {string} The spacing value (px). */ export function space( value = 1 ) { diff --git a/packages/components/src/utils/values.js b/packages/components/src/utils/values.js index d8d63750290276..5bf94b6ece8415 100644 --- a/packages/components/src/utils/values.js +++ b/packages/components/src/utils/values.js @@ -1,31 +1,41 @@ +/* eslint-disable jsdoc/valid-types */ /** * Determines if a value is null or undefined. * - * @param {any} value The value to check. - * @return {boolean} Whether value is null or undefined. + * @template T + * + * @param {T | null | undefined} value The value to check. + * @return {value is T} Whether value is not null or undefined. */ export function isValueDefined( value ) { return value !== undefined && value !== null; } +/* eslint-enable jsdoc/valid-types */ +/* eslint-disable jsdoc/valid-types */ /** * Determines if a value is empty, null, or undefined. * - * @param {any} value The value to check. - * @return {boolean} Whether value is empty. + * @template T + * + * @param {T | "" | null | undefined} value The value to check. + * @return {value is T} Whether value is empty. */ export function isValueEmpty( value ) { const isEmptyString = value === ''; return ! isValueDefined( value ) || isEmptyString; } +/* eslint-enable jsdoc/valid-types */ /** - * Attempts to get a defined/non-null value from a collection of arguments. + * Get the first defined/non-null value from an array. + * + * @template T * - * @param {Array<any>} values Values to derive from. - * @param {any} fallbackValue Fallback value if there are no defined values. - * @return {any} A defined value or the fallback value. + * @param {Array<T | null | undefined>} values Values to derive from. + * @param {T} fallbackValue Fallback value if there are no defined values. + * @return {T} A defined value or the fallback value. */ export function getDefinedValue( values = [], fallbackValue ) { return values.find( isValueDefined ) ?? fallbackValue; diff --git a/packages/components/src/visually-hidden/index.js b/packages/components/src/visually-hidden/index.js index 727e958787c2da..5ce7e3ff6cca2e 100644 --- a/packages/components/src/visually-hidden/index.js +++ b/packages/components/src/visually-hidden/index.js @@ -8,13 +8,25 @@ import classnames from 'classnames'; */ import { renderAsRenderProps } from './utils'; +/** + * @template {keyof JSX.IntrinsicElements | import('react').JSXElementConstructor<any>} T + * @typedef OwnProps + * @property {T} [as='div'] Component to render, e.g. `"div"` or `MyComponent`. + */ + +/** + * @template {keyof JSX.IntrinsicElements | import('react').JSXElementConstructor<any>} T + * @typedef {OwnProps<T> & import('react').ComponentProps<T>} Props + */ + /** * VisuallyHidden component to render text out non-visually * for use in devices such as a screen reader. * - * @param {Object} props Component props. - * @param {string|WPComponent} [props.as="div"] A tag or component to render. - * @param {string} [props.className] Class to set on the container. + * @template {keyof JSX.IntrinsicElements | import('react').JSXElementConstructor<any>} T + * + * @param {Props<T>} props + * @return {JSX.Element} Element */ function VisuallyHidden( { as = 'div', className, ...props } ) { return renderAsRenderProps( { diff --git a/packages/components/src/visually-hidden/utils.js b/packages/components/src/visually-hidden/utils.js index 70bcfac3aeb7d7..d8be9e759c6145 100644 --- a/packages/components/src/visually-hidden/utils.js +++ b/packages/components/src/visually-hidden/utils.js @@ -1,5 +1,13 @@ /** - * Utility Functions + * @template {keyof JSX.IntrinsicElements | import('react').JSXElementConstructor<any>} T + * @typedef OwnProps + * @property {T} [as='div'] Component to render + * @property {import('react').ReactNode | ((props: import('react').ComponentProps<T>) => JSX.Element) } [children] Children or render props function + */ + +/** + * @template {keyof JSX.IntrinsicElements | import('react').JSXElementConstructor<any>} T + * @typedef {OwnProps<T> & import('react').ComponentProps<T>} Props */ /** @@ -9,8 +17,9 @@ * * See VisuallyHidden hidden for example. * - * @param {string|WPComponent} as A tag or component to render. - * @return {WPComponent} The rendered component. + * @template {keyof JSX.IntrinsicElements | import('react').JSXElementConstructor<any>} T + * @param {Props<T>} props + * @return {JSX.Element} The rendered element. */ function renderAsRenderProps( { as: Component = 'div', ...props } ) { if ( typeof props.children === 'function' ) { diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index 9267432f4f8914..c697cb71d98c17 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -2,10 +2,28 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "rootDir": "src", - "declarationDir": "build-types", + "declarationDir": "build-types" }, - "references": [], - "include": [ - "src/dashicon/*" + "references": [ + { "path": "../deprecated" }, + { "path": "../element" }, + { "path": "../hooks" }, + { "path": "../primitives" } ], + "include": [ + "src/animate/**/*", + "src/base-control/**/*", + "src/dashicon/**/*", + "src/tip/**/*", + "src/utils/**/*", + "src/visually-hidden/**/*" + ], + "exclude": [ + "src/**/*.android.js", + "src/**/*.ios.js", + "src/**/*.native.js", + "src/**/react-native-*", + "src/**/stories", + "src/**/test" + ] } diff --git a/packages/compose/README.md b/packages/compose/README.md index 3c9ad543080dcc..6fa1842842c048 100644 --- a/packages/compose/README.md +++ b/packages/compose/README.md @@ -132,6 +132,31 @@ _Returns_ - `Array`: Async array. +<a name="useConstrainedTabbing" href="#useConstrainedTabbing">#</a> **useConstrainedTabbing** + +In Dialogs/modals, the tabbing must be constrained to the content of +the wrapper element. This hook adds the behavior to the returned ref. + +_Usage_ + +```js +import { useConstrainedTabbing } from '@wordpress/compose'; + +const ConstrainedTabbingExample = () => { + const constrainedTabbingRef = useConstrainedTabbing() + return ( + <div ref={ constrainedTabbingRef }> + <Button /> + <Button /> + </div> + ); +} +``` + +_Returns_ + +- `(Object|Function)`: Element Ref. + <a name="useCopyOnClick" href="#useCopyOnClick">#</a> **useCopyOnClick** Copies the text to the clipboard when the element is clicked. @@ -157,6 +182,69 @@ _Parameters_ - _args_ `...any`: Arguments passed to Lodash's `debounce`. +_Returns_ + +- `Function`: Debounced function. + +<a name="useFocusOnMount" href="#useFocusOnMount">#</a> **useFocusOnMount** + +Hook used to focus the first tabbable element on mount. + +_Usage_ + +```js +import { useFocusOnMount } from '@wordpress/compose'; + +const WithFocusOnMount = () => { + const ref = useFocusOnMount() + return ( + <div ref={ ref }> + <Button /> + <Button /> + </div> + ); +} +``` + +_Parameters_ + +- _focusOnMount_ `(boolean|string)`: Focus on mount mode. + +_Returns_ + +- `Function`: Ref callback. + +<a name="useFocusReturn" href="#useFocusReturn">#</a> **useFocusReturn** + +When opening modals/sidebars/dialogs, the focus +must move to the opened area and return to the +previously focused element when closed. +The current hook implements the returning behavior. + +_Usage_ + +```js +import { useFocusReturn } from '@wordpress/compose'; + +const WithFocusReturn = () => { + const ref = useFocusReturn() + return ( + <div ref={ ref }> + <Button /> + <Button /> + </div> + ); +} +``` + +_Parameters_ + +- _onFocusReturn_ `?Function`: Overrides the default return behavior. + +_Returns_ + +- `Function`: Element Ref. + <a name="useInstanceId" href="#useInstanceId">#</a> **useInstanceId** Provides a unique instance ID. @@ -233,6 +321,21 @@ _Returns_ - `Array`: An array of {Element} `resizeListener` and {?Object} `sizes` with properties `width` and `height` +<a name="useThrottle" href="#useThrottle">#</a> **useThrottle** + +Throttles a function with Lodash's `throttle`. A new throttled function will +be returned and any scheduled calls cancelled if any of the arguments change, +including the function to throttle, so please wrap functions created on +render in components in `useCallback`. + +_Parameters_ + +- _args_ `...any`: Arguments passed to Lodash's `throttle`. + +_Returns_ + +- `Function`: Throttled function. + <a name="useViewportMatch" href="#useViewportMatch">#</a> **useViewportMatch** Returns true if the viewport matches the given query, or false otherwise. @@ -276,6 +379,8 @@ _Parameters_ <a name="withGlobalEvents" href="#withGlobalEvents">#</a> **withGlobalEvents** +> **Deprecated** + Higher-order component creator which, given an object of DOM event types and values corresponding to a callback function name on the component, will create or update a window event handler to invoke the callback when an event diff --git a/packages/compose/package.json b/packages/compose/package.json index 016c72c322c815..8fd5c64d8e2e9f 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -25,13 +25,18 @@ "react-native": "src/index", "sideEffects": false, "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", "@wordpress/element": "file:../element", "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keycodes": "file:../keycodes", "@wordpress/priority-queue": "file:../priority-queue", "clipboard": "^2.0.1", "lodash": "^4.17.19", + "memize": "^1.1.0", "mousetrap": "^1.6.5", + "react-merge-refs": "^1.0.0", "react-resize-aware": "^3.0.1", "use-memo-one": "^1.1.1" }, diff --git a/packages/compose/src/higher-order/with-global-events/README.md b/packages/compose/src/higher-order/with-global-events/README.md index 7bcf4c3044d057..24692ffbeb2b8b 100644 --- a/packages/compose/src/higher-order/with-global-events/README.md +++ b/packages/compose/src/higher-order/with-global-events/README.md @@ -1,6 +1,8 @@ withGlobalEvents ================ +**Deprecated** + `withGlobalEvents` is a higher-order component used to facilitate responding to global events, where one would otherwise use `window.addEventListener`. On behalf of the consuming developer, the higher-order component manages: diff --git a/packages/compose/src/higher-order/with-global-events/index.js b/packages/compose/src/higher-order/with-global-events/index.js index 6b2bc53ebeda57..9c77c3ab2c4e5e 100644 --- a/packages/compose/src/higher-order/with-global-events/index.js +++ b/packages/compose/src/higher-order/with-global-events/index.js @@ -7,6 +7,7 @@ import { forEach } from 'lodash'; * WordPress dependencies */ import { Component, forwardRef } from '@wordpress/element'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies @@ -29,6 +30,8 @@ const listener = new Listener(); * manages unbinding when the component unmounts, and binding at most a single * event handler for the entire application. * + * @deprecated + * * @param {Object<string,string>} eventTypesToHandlers Object with keys of DOM * event type, the value a * name of the function on @@ -38,7 +41,11 @@ const listener = new Listener(); * * @return {Function} Higher-order component. */ -function withGlobalEvents( eventTypesToHandlers ) { +export default function withGlobalEvents( eventTypesToHandlers ) { + deprecated( 'wp.compose.withGlobalEvents', { + alternative: 'useEffect', + } ); + return createHigherOrderComponent( ( WrappedComponent ) => { class Wrapper extends Component { constructor() { @@ -92,5 +99,3 @@ function withGlobalEvents( eventTypesToHandlers ) { } ); }, 'withGlobalEvents' ); } - -export default withGlobalEvents; diff --git a/packages/compose/src/higher-order/with-global-events/test/index.js b/packages/compose/src/higher-order/with-global-events/test/index.js index 36778ad4121e84..a9ccb39db6deda 100644 --- a/packages/compose/src/higher-order/with-global-events/test/index.js +++ b/packages/compose/src/higher-order/with-global-events/test/index.js @@ -72,6 +72,7 @@ describe( 'withGlobalEvents', () => { it( 'renders with original component', () => { mountEnhancedComponent(); + expect( console ).toHaveWarned(); expect( wrapper.root.findByType( 'div' ).children[ 0 ] ).toBe( 'Hello' ); diff --git a/packages/compose/src/hooks/use-callback-ref/index.js b/packages/compose/src/hooks/use-callback-ref/index.js new file mode 100644 index 00000000000000..0877607027acf6 --- /dev/null +++ b/packages/compose/src/hooks/use-callback-ref/index.js @@ -0,0 +1,20 @@ +/** + * External dependencies + */ +import memize from 'memize'; + +/** + * WordPress dependencies + */ +import { useCallback } from '@wordpress/element'; + +function useCallbackRef( callback, deps ) { + return useCallback( + memize( callback, { + maxSize: 1, + } ), + deps + ); +} + +export default useCallbackRef; diff --git a/packages/compose/src/hooks/use-constrained-tabbing/README.md b/packages/compose/src/hooks/use-constrained-tabbing/README.md new file mode 100644 index 00000000000000..eb795cf654d403 --- /dev/null +++ b/packages/compose/src/hooks/use-constrained-tabbing/README.md @@ -0,0 +1,28 @@ +`useConstrainedTabbing` +====================== + +In Dialogs/modals, the tabbing must be constrained to the content of the wrapper element. To achieve this behavior you can use the `useConstrainedTabbing` hook. + +## Return Object Properties + +### `ref` + +- Type: `Object|Function` + +A function reference that must be passed to the DOM element where constrained tabbing should be enabled. + +## Usage + +```jsx +import { useConstrainedTabbing } from '@wordpress/compose'; + +const ConstrainedTabbingExample = () => { + const ref = useConstrainedTabbing() + return ( + <div ref={ ref }> + <Button /> + <Button /> + </div> + ); +}; +``` diff --git a/packages/compose/src/hooks/use-constrained-tabbing/index.js b/packages/compose/src/hooks/use-constrained-tabbing/index.js new file mode 100644 index 00000000000000..55011151a6a74d --- /dev/null +++ b/packages/compose/src/hooks/use-constrained-tabbing/index.js @@ -0,0 +1,70 @@ +/** + * WordPress dependencies + */ +import { TAB } from '@wordpress/keycodes'; +import { focus } from '@wordpress/dom'; + +/** + * Internal dependencies + */ +import useCallbackRef from '../use-callback-ref'; + +/** + * In Dialogs/modals, the tabbing must be constrained to the content of + * the wrapper element. This hook adds the behavior to the returned ref. + * + * @return {Object|Function} Element Ref. + * + * @example + * ```js + * import { useConstrainedTabbing } from '@wordpress/compose'; + * + * const ConstrainedTabbingExample = () => { + * const constrainedTabbingRef = useConstrainedTabbing() + * return ( + * <div ref={ constrainedTabbingRef }> + * <Button /> + * <Button /> + * </div> + * ); + * } + * ``` + */ +function useConstrainedTabbing() { + const ref = useCallbackRef( ( node ) => { + if ( ! node ) { + return; + } + node.addEventListener( 'keydown', ( event ) => { + if ( event.keyCode !== TAB ) { + return; + } + + const tabbables = focus.tabbable.find( node ); + if ( ! tabbables.length ) { + return; + } + const firstTabbable = tabbables[ 0 ]; + const lastTabbable = tabbables[ tabbables.length - 1 ]; + + if ( event.shiftKey && event.target === firstTabbable ) { + event.preventDefault(); + lastTabbable.focus(); + } else if ( ! event.shiftKey && event.target === lastTabbable ) { + event.preventDefault(); + firstTabbable.focus(); + /* + * When pressing Tab and none of the tabbables has focus, the keydown + * event happens on the wrapper div: move focus on the first tabbable. + */ + } else if ( ! tabbables.includes( event.target ) ) { + event.preventDefault(); + firstTabbable.focus(); + } + } ); + }, [] ); + + return ref; +} + +export default useConstrainedTabbing; diff --git a/packages/compose/src/hooks/use-constrained-tabbing/index.native.js b/packages/compose/src/hooks/use-constrained-tabbing/index.native.js new file mode 100644 index 00000000000000..e67ada8e203b91 --- /dev/null +++ b/packages/compose/src/hooks/use-constrained-tabbing/index.native.js @@ -0,0 +1,14 @@ +/** + * WordPress dependencies + */ +import { useRef } from '@wordpress/element'; + +function useConstrainedTabbing() { + const ref = useRef(); + + // Do nothing on mobile as tabbing is not a mobile behavior. + + return ref; +} + +export default useConstrainedTabbing; diff --git a/packages/compose/src/hooks/use-debounce/index.js b/packages/compose/src/hooks/use-debounce/index.js index 1a901ed0ebea64..405bb7a7a4e36f 100644 --- a/packages/compose/src/hooks/use-debounce/index.js +++ b/packages/compose/src/hooks/use-debounce/index.js @@ -16,6 +16,8 @@ import { useEffect } from '@wordpress/element'; * render in components in `useCallback`. * * @param {...any} args Arguments passed to Lodash's `debounce`. + * + * @return {Function} Debounced function. */ export default function useDebounce( ...args ) { const debounced = useMemoOne( () => debounce( ...args ), args ); diff --git a/packages/compose/src/hooks/use-dialog/README.md b/packages/compose/src/hooks/use-dialog/README.md new file mode 100644 index 00000000000000..6112326c41aefa --- /dev/null +++ b/packages/compose/src/hooks/use-dialog/README.md @@ -0,0 +1,44 @@ +`useDialog` +=========== + +React hook to be used on a dialog wrapper to enable the following behaviors: + + - constrained tabbing. + - focus on mount. + - return focus on unmount. + - focus outside. + +## Returned value + +The hooks returns an array composed of the two following values: + +### `ref` + +- Type: `Object|Function` + +A React ref that must be passed to the DOM element where the behavior should be attached. + +### `props` + +- Type: `Object` + +Extra props to apply to the wrapper. + +## Usage + +```jsx +import { __experimentalUseDialog as useDialog } from '@wordpress/compose'; + +const MyDialog = () => { + const [ ref, extraProps ] = useDialog( { + onClose: () => console.log('do something to close the dialog') + } ); + + return ( + <div ref={ ref } {...extraProps}> + <Button /> + <Button /> + </div> + ); +}; +``` diff --git a/packages/compose/src/hooks/use-dialog/index.js b/packages/compose/src/hooks/use-dialog/index.js new file mode 100644 index 00000000000000..da97c877368e63 --- /dev/null +++ b/packages/compose/src/hooks/use-dialog/index.js @@ -0,0 +1,64 @@ +/** + * External dependencies + */ +import mergeRefs from 'react-merge-refs'; + +/** + * WordPress dependencies + */ +import { useRef, useEffect } from '@wordpress/element'; +import { ESCAPE } from '@wordpress/keycodes'; + +/** + * Internal dependencies + */ +import useConstrainedTabbing from '../use-constrained-tabbing'; +import useFocusOnMount from '../use-focus-on-mount'; +import useFocusReturn from '../use-focus-return'; +import useFocusOutside from '../use-focus-outside'; +import useCallbackRef from '../use-callback-ref'; + +/** + * Returns a ref and props to apply to a dialog wrapper to enable the following behaviors: + * - constrained tabbing. + * - focus on mount. + * - return focus on unmount. + * - focus outside. + * + * @param {Object} options Dialog Options. + */ +function useDialog( options ) { + const onClose = useRef(); + useEffect( () => { + onClose.current = options.onClose; + }, [ options.onClose ] ); + const constrainedTabbingRef = useConstrainedTabbing(); + const focusOnMountRef = useFocusOnMount(); + const focusReturnRef = useFocusReturn(); + const focusOutsideProps = useFocusOutside( options.onClose ); + const closeOnEscapeRef = useCallbackRef( ( node ) => { + if ( ! node ) { + return; + } + + node.addEventListener( 'keydown', ( event ) => { + // Close on escape + if ( event.keyCode === ESCAPE && onClose.current ) { + event.stopPropagation(); + onClose.current(); + } + } ); + }, [] ); + + return [ + mergeRefs( [ + constrainedTabbingRef, + focusReturnRef, + focusOnMountRef, + closeOnEscapeRef, + ] ), + focusOutsideProps, + ]; +} + +export default useDialog; diff --git a/packages/compose/src/hooks/use-focus-on-mount/README.md b/packages/compose/src/hooks/use-focus-on-mount/README.md new file mode 100644 index 00000000000000..c5d32f05bd145d --- /dev/null +++ b/packages/compose/src/hooks/use-focus-on-mount/README.md @@ -0,0 +1,28 @@ +`useFocusOnMount` +================= + +Hook used to focus the first tabbable element on mount. + +## Return Object Properties + +### `ref` + +- Type: `Object|Function` + +A React ref that must be passed to the DOM element where the behavior should be attached. + +## Usage + +```jsx +import { useFocusOnMount } from '@wordpress/compose'; + +const WithFocusOnMount = () => { + const ref = useFocusOnMount() + return ( + <div ref={ ref }> + <Button /> + <Button /> + </div> + ); +}; +``` diff --git a/packages/compose/src/hooks/use-focus-on-mount/index.js b/packages/compose/src/hooks/use-focus-on-mount/index.js new file mode 100644 index 00000000000000..54492d8b140f75 --- /dev/null +++ b/packages/compose/src/hooks/use-focus-on-mount/index.js @@ -0,0 +1,60 @@ +/** + * WordPress dependencies + */ +import { useRef, useEffect } from '@wordpress/element'; +import { focus } from '@wordpress/dom'; + +/** + * Internal dependencies + */ +import useCallbackRef from '../use-callback-ref'; + +/** + * Hook used to focus the first tabbable element on mount. + * + * @param {boolean|string} focusOnMount Focus on mount mode. + * @return {Function} Ref callback. + * + * @example + * ```js + * import { useFocusOnMount } from '@wordpress/compose'; + * + * const WithFocusOnMount = () => { + * const ref = useFocusOnMount() + * return ( + * <div ref={ ref }> + * <Button /> + * <Button /> + * </div> + * ); + * } + * ``` + */ +export default function useFocusOnMount( focusOnMount = 'firstElement' ) { + const focusOnMountRef = useRef( focusOnMount ); + useEffect( () => { + focusOnMountRef.current = focusOnMount; + }, [ focusOnMount ] ); + + return useCallbackRef( ( node ) => { + if ( ! node || focusOnMountRef.current === false ) { + return; + } + + if ( node.contains( node.ownerDocument.activeElement ) ) { + return; + } + + let target = node; + + if ( focusOnMountRef.current === 'firstElement' ) { + const firstTabbable = focus.tabbable.find( node )[ 0 ]; + + if ( firstTabbable ) { + target = firstTabbable; + } + } + + target.focus(); + }, [] ); +} diff --git a/packages/compose/src/hooks/use-focus-outside/index.js b/packages/compose/src/hooks/use-focus-outside/index.js new file mode 100644 index 00000000000000..2169d4fd83c371 --- /dev/null +++ b/packages/compose/src/hooks/use-focus-outside/index.js @@ -0,0 +1,191 @@ +/** + * External dependencies + */ +import { includes } from 'lodash'; + +/** + * WordPress dependencies + */ +import { useCallback, useEffect, useRef } from '@wordpress/element'; + +/** + * Input types which are classified as button types, for use in considering + * whether element is a (focus-normalized) button. + * + * @type {string[]} + */ +const INPUT_BUTTON_TYPES = [ 'button', 'submit' ]; + +/** + * @typedef {HTMLButtonElement | HTMLLinkElement | HTMLInputElement} FocusNormalizedButton + */ + +// Disable reason: Rule doesn't support predicate return types +/* eslint-disable jsdoc/valid-types */ +/** + * Returns true if the given element is a button element subject to focus + * normalization, or false otherwise. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus + * + * @param {EventTarget} eventTarget The target from a mouse or touch event. + * + * @return {eventTarget is FocusNormalizedButton} Whether element is a button. + */ +function isFocusNormalizedButton( eventTarget ) { + if ( ! ( eventTarget instanceof window.HTMLElement ) ) { + return false; + } + switch ( eventTarget.nodeName ) { + case 'A': + case 'BUTTON': + return true; + + case 'INPUT': + return includes( + INPUT_BUTTON_TYPES, + /** @type {HTMLInputElement} */ ( eventTarget ).type + ); + } + + return false; +} +/* eslint-enable jsdoc/valid-types */ + +/** + * @typedef {import('react').SyntheticEvent} SyntheticEvent + */ + +/** + * @callback EventCallback + * @param {SyntheticEvent} event input related event. + */ + +/** + * @typedef FocusOutsideReactElement + * @property {EventCallback} handleFocusOutside callback for a focus outside event. + */ + +/** + * @typedef {import('react').MutableRefObject<FocusOutsideReactElement | undefined>} FocusOutsideRef + */ + +/** + * @typedef {Object} FocusOutsideReturnValue + * @property {EventCallback} onFocus An event handler for focus events. + * @property {EventCallback} onBlur An event handler for blur events. + * @property {EventCallback} onMouseDown An event handler for mouse down events. + * @property {EventCallback} onMouseUp An event handler for mouse up events. + * @property {EventCallback} onTouchStart An event handler for touch start events. + * @property {EventCallback} onTouchEnd An event handler for touch end events. + */ + +/** + * A react hook that can be used to check whether focus has moved outside the + * element the event handlers are bound to. + * + * @param {EventCallback} onFocusOutside A callback triggered when focus moves outside + * the element the event handlers are bound to. + * + * @return {FocusOutsideReturnValue} An object containing event handlers. Bind the event handlers + * to a wrapping element element to capture when focus moves + * outside that element. + */ +export default function useFocusOutside( onFocusOutside ) { + const currentOnFocusOutside = useRef( onFocusOutside ); + useEffect( () => { + currentOnFocusOutside.current = onFocusOutside; + }, [ onFocusOutside ] ); + + const preventBlurCheck = useRef( false ); + + /** + * @type {import('react').MutableRefObject<number | undefined>} + */ + const blurCheckTimeoutId = useRef(); + + /** + * Cancel a blur check timeout. + */ + const cancelBlurCheck = useCallback( () => { + clearTimeout( blurCheckTimeoutId.current ); + }, [] ); + + // Cancel blur checks on unmount. + useEffect( () => { + return () => cancelBlurCheck(); + }, [] ); + + // Cancel a blur check if the callback or ref is no longer provided. + useEffect( () => { + if ( ! onFocusOutside ) { + cancelBlurCheck(); + } + }, [ onFocusOutside, cancelBlurCheck ] ); + + /** + * Handles a mousedown or mouseup event to respectively assign and + * unassign a flag for preventing blur check on button elements. Some + * browsers, namely Firefox and Safari, do not emit a focus event on + * button elements when clicked, while others do. The logic here + * intends to normalize this as treating click on buttons as focus. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus + * + * @param {SyntheticEvent} event Event for mousedown or mouseup. + */ + const normalizeButtonFocus = useCallback( ( event ) => { + const { type, target } = event; + const isInteractionEnd = includes( [ 'mouseup', 'touchend' ], type ); + + if ( isInteractionEnd ) { + preventBlurCheck.current = false; + } else if ( isFocusNormalizedButton( target ) ) { + preventBlurCheck.current = true; + } + }, [] ); + + /** + * A callback triggered when a blur event occurs on the element the handler + * is bound to. + * + * Calls the `onFocusOutside` callback in an immediate timeout if focus has + * move outside the bound element and is still within the document. + * + * @param {SyntheticEvent} event Blur event. + */ + const queueBlurCheck = useCallback( ( event ) => { + // React does not allow using an event reference asynchronously + // due to recycling behavior, except when explicitly persisted. + event.persist(); + + // Skip blur check if clicking button. See `normalizeButtonFocus`. + if ( preventBlurCheck.current ) { + return; + } + + blurCheckTimeoutId.current = setTimeout( () => { + // If document is not focused then focus should remain + // inside the wrapped component and therefore we cancel + // this blur event thereby leaving focus in place. + // https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus. + if ( ! document.hasFocus() ) { + event.preventDefault(); + return; + } + + if ( 'function' === typeof currentOnFocusOutside.current ) { + currentOnFocusOutside.current( event ); + } + }, 0 ); + }, [] ); + + return { + onFocus: cancelBlurCheck, + onMouseDown: normalizeButtonFocus, + onMouseUp: normalizeButtonFocus, + onTouchStart: normalizeButtonFocus, + onTouchEnd: normalizeButtonFocus, + onBlur: queueBlurCheck, + }; +} diff --git a/packages/compose/src/hooks/use-focus-outside/index.native.js b/packages/compose/src/hooks/use-focus-outside/index.native.js new file mode 100644 index 00000000000000..6c5c35766379f9 --- /dev/null +++ b/packages/compose/src/hooks/use-focus-outside/index.native.js @@ -0,0 +1,179 @@ +/** + * External dependencies + */ +import { includes } from 'lodash'; + +/** + * WordPress dependencies + */ +import { useCallback, useEffect, useRef } from '@wordpress/element'; + +/** + * Input types which are classified as button types, for use in considering + * whether element is a (focus-normalized) button. + * + * @type {string[]} + */ +const INPUT_BUTTON_TYPES = [ 'button', 'submit' ]; + +/** + * @typedef {HTMLButtonElement | HTMLLinkElement | HTMLInputElement} FocusNormalizedButton + */ + +// Disable reason: Rule doesn't support predicate return types +/* eslint-disable jsdoc/valid-types */ +/** + * Returns true if the given element is a button element subject to focus + * normalization, or false otherwise. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus + * + * @param {EventTarget} eventTarget The target from a mouse or touch event. + * + * @return {eventTarget is FocusNormalizedButton} Whether element is a button. + */ +function isFocusNormalizedButton( eventTarget ) { + switch ( eventTarget.nodeName ) { + case 'A': + case 'BUTTON': + return true; + + case 'INPUT': + return includes( + INPUT_BUTTON_TYPES, + /** @type {HTMLInputElement} */ ( eventTarget ).type + ); + } + + return false; +} +/* eslint-enable jsdoc/valid-types */ + +/** + * @typedef {import('react').SyntheticEvent} SyntheticEvent + */ + +/** + * @callback EventCallback + * @param {SyntheticEvent} event input related event. + */ + +/** + * @typedef FocusOutsideReactElement + * @property {EventCallback} handleFocusOutside callback for a focus outside event. + */ + +/** + * @typedef {import('react').MutableRefObject<FocusOutsideReactElement | undefined>} FocusOutsideRef + */ + +/** + * @typedef {Object} FocusOutsideReturnValue + * @property {EventCallback} onFocus An event handler for focus events. + * @property {EventCallback} onBlur An event handler for blur events. + * @property {EventCallback} onMouseDown An event handler for mouse down events. + * @property {EventCallback} onMouseUp An event handler for mouse up events. + * @property {EventCallback} onTouchStart An event handler for touch start events. + * @property {EventCallback} onTouchEnd An event handler for touch end events. + */ + +/** + * A react hook that can be used to check whether focus has moved outside the + * element the event handlers are bound to. + * + * @param {EventCallback} onFocusOutside A callback triggered when focus moves outside + * the element the event handlers are bound to. + * + * @return {FocusOutsideReturnValue} An object containing event handlers. Bind the event handlers + * to a wrapping element element to capture when focus moves + * outside that element. + */ +export default function useFocusOutside( onFocusOutside ) { + const currentOnFocusOutside = useRef( onFocusOutside ); + useEffect( () => { + currentOnFocusOutside.current = onFocusOutside; + }, [ onFocusOutside ] ); + + const preventBlurCheck = useRef( false ); + + /** + * @type {import('react').MutableRefObject<number | undefined>} + */ + const blurCheckTimeoutId = useRef(); + + /** + * Cancel a blur check timeout. + */ + const cancelBlurCheck = useCallback( () => { + clearTimeout( blurCheckTimeoutId.current ); + }, [] ); + + // Cancel blur checks on unmount. + useEffect( () => { + return () => cancelBlurCheck(); + }, [] ); + + // Cancel a blur check if the callback or ref is no longer provided. + useEffect( () => { + if ( ! onFocusOutside ) { + cancelBlurCheck(); + } + }, [ onFocusOutside, cancelBlurCheck ] ); + + /** + * Handles a mousedown or mouseup event to respectively assign and + * unassign a flag for preventing blur check on button elements. Some + * browsers, namely Firefox and Safari, do not emit a focus event on + * button elements when clicked, while others do. The logic here + * intends to normalize this as treating click on buttons as focus. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus + * + * @param {SyntheticEvent} event Event for mousedown or mouseup. + */ + const normalizeButtonFocus = useCallback( ( event ) => { + const { type, target } = event; + const isInteractionEnd = includes( [ 'mouseup', 'touchend' ], type ); + + if ( isInteractionEnd ) { + preventBlurCheck.current = false; + } else if ( isFocusNormalizedButton( target ) ) { + preventBlurCheck.current = true; + } + }, [] ); + + /** + * A callback triggered when a blur event occurs on the element the handler + * is bound to. + * + * Calls the `onFocusOutside` callback in an immediate timeout if focus has + * move outside the bound element and is still within the document. + * + * @param {SyntheticEvent} event Blur event. + */ + const queueBlurCheck = useCallback( ( event ) => { + // React does not allow using an event reference asynchronously + // due to recycling behavior, except when explicitly persisted. + event.persist(); + + // Skip blur check if clicking button. See `normalizeButtonFocus`. + if ( preventBlurCheck.current ) { + return; + } + + blurCheckTimeoutId.current = setTimeout( () => { + if ( 'function' === typeof currentOnFocusOutside.current ) { + currentOnFocusOutside.current( event ); + } + }, 0 ); + }, [] ); + + return { + onFocus: cancelBlurCheck, + onMouseDown: normalizeButtonFocus, + onMouseUp: normalizeButtonFocus, + onTouchStart: normalizeButtonFocus, + onTouchEnd: normalizeButtonFocus, + onBlur: queueBlurCheck, + }; +} diff --git a/packages/compose/src/hooks/use-focus-outside/test/index.js b/packages/compose/src/hooks/use-focus-outside/test/index.js new file mode 100644 index 00000000000000..90a27102f90813 --- /dev/null +++ b/packages/compose/src/hooks/use-focus-outside/test/index.js @@ -0,0 +1,124 @@ +/** + * External dependencies + */ +import TestUtils from 'react-dom/test-utils'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import useFocusOutside from '../'; +import ReactDOM from 'react-dom'; + +let wrapper, onFocusOutside; + +describe( 'useFocusOutside', () => { + let origHasFocus; + + const FocusOutsideComponent = ( { onFocusOutside: callback } ) => ( + <div { ...useFocusOutside( callback ) }> + <input /> + <input type="button" /> + </div> + ); + + // this is needed because TestUtils does not accept a stateless component. + // anything run through a HOC ends up as a stateless component. + const getTestComponent = ( WrappedComponent, props ) => { + class TestComponent extends Component { + render() { + return <WrappedComponent { ...props } />; + } + } + return <TestComponent />; + }; + + const simulateEvent = ( event, index = 0 ) => { + const element = TestUtils.scryRenderedDOMComponentsWithTag( + wrapper, + 'input' + ); + TestUtils.Simulate[ event ]( element[ index ] ); + }; + + beforeEach( () => { + // Mock document.hasFocus() to always be true for testing + // note: we overide this for some tests. + origHasFocus = document.hasFocus; + document.hasFocus = () => true; + + onFocusOutside = jest.fn(); + wrapper = TestUtils.renderIntoDocument( + getTestComponent( FocusOutsideComponent, { onFocusOutside } ) + ); + } ); + + afterEach( () => { + document.hasFocus = origHasFocus; + } ); + + it( 'should not call handler if focus shifts to element within component', () => { + simulateEvent( 'focus' ); + simulateEvent( 'blur' ); + simulateEvent( 'focus', 1 ); + + jest.runAllTimers(); + + expect( onFocusOutside ).not.toHaveBeenCalled(); + } ); + + it( 'should not call handler if focus transitions via click to button', () => { + simulateEvent( 'focus' ); + simulateEvent( 'mouseDown', 1 ); + simulateEvent( 'blur' ); + + // In most browsers, the input at index 1 would receive a focus event + // at this point, but this is not guaranteed, which is the intention of + // the normalization behavior tested here. + simulateEvent( 'mouseUp', 1 ); + + jest.runAllTimers(); + + expect( onFocusOutside ).not.toHaveBeenCalled(); + } ); + + it( 'should call handler if focus doesn’t shift to element within component', () => { + simulateEvent( 'focus' ); + simulateEvent( 'blur' ); + + jest.runAllTimers(); + + expect( onFocusOutside ).toHaveBeenCalled(); + } ); + + it( 'should not call handler if focus shifts outside the component when the document does not have focus', () => { + // Force document.hasFocus() to return false to simulate the window/document losing focus + // See https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus. + document.hasFocus = () => false; + + simulateEvent( 'focus' ); + simulateEvent( 'blur' ); + + jest.runAllTimers(); + + expect( onFocusOutside ).not.toHaveBeenCalled(); + } ); + + it( 'should cancel check when unmounting while queued', () => { + simulateEvent( 'focus' ); + simulateEvent( 'input' ); + + ReactDOM.unmountComponentAtNode( + // eslint-disable-next-line react/no-find-dom-node + ReactDOM.findDOMNode( wrapper ).parentNode + ); + + jest.runAllTimers(); + + expect( onFocusOutside ).not.toHaveBeenCalled(); + } ); +} ); diff --git a/packages/compose/src/hooks/use-focus-return/README.md b/packages/compose/src/hooks/use-focus-return/README.md new file mode 100644 index 00000000000000..63bd7b088fb3df --- /dev/null +++ b/packages/compose/src/hooks/use-focus-return/README.md @@ -0,0 +1,28 @@ +`useFocusReturn` +================ + +When opening modals/sidebars/dialogs, the focus must move to the opened area and return to the previously focused element when closed. The current hook implements the returning behavior. + +## Return Object Properties + +### `ref` + +- Type: `Function` + +A function reference that must be passed to the DOM element being mounted and which needs to return the focus to its original position when unmounted. + +## Usage + +```jsx +import { useFocusReturn } from '@wordpress/compose'; + +const WithFocusReturn = () => { + const ref = useFocusReturn() + return ( + <div ref={ ref }> + <Button /> + <Button /> + </div> + ); +}; +``` diff --git a/packages/compose/src/hooks/use-focus-return/index.js b/packages/compose/src/hooks/use-focus-return/index.js new file mode 100644 index 00000000000000..2886b08125ad40 --- /dev/null +++ b/packages/compose/src/hooks/use-focus-return/index.js @@ -0,0 +1,76 @@ +/** + * WordPress dependencies + */ +import { useRef, useEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import useCallbackRef from '../use-callback-ref'; + +/** + * When opening modals/sidebars/dialogs, the focus + * must move to the opened area and return to the + * previously focused element when closed. + * The current hook implements the returning behavior. + * + * @param {Function?} onFocusReturn Overrides the default return behavior. + * @return {Function} Element Ref. + * + * @example + * ```js + * import { useFocusReturn } from '@wordpress/compose'; + * + * const WithFocusReturn = () => { + * const ref = useFocusReturn() + * return ( + * <div ref={ ref }> + * <Button /> + * <Button /> + * </div> + * ); + * } + * ``` + */ +function useFocusReturn( onFocusReturn ) { + const ref = useRef(); + const focusedBeforeMount = useRef(); + const onFocusReturnRef = useRef( onFocusReturn ); + useEffect( () => { + onFocusReturnRef.current = onFocusReturn; + }, [ onFocusReturn ] ); + + return useCallbackRef( ( node ) => { + if ( node ) { + // Set ref to be used when unmounting. + ref.current = node; + + // Only set when the node mounts. + if ( focusedBeforeMount.current ) { + return; + } + + focusedBeforeMount.current = node.ownerDocument.activeElement; + } else if ( focusedBeforeMount.current ) { + const isFocused = ref.current.contains( + ref.current.ownerDocument.activeElement + ); + + if ( ! isFocused ) { + return; + } + + // Defer to the component's own explicit focus return behavior, if + // specified. This allows for support that the `onFocusReturn` + // decides to allow the default behavior to occur under some + // conditions. + if ( onFocusReturnRef.current ) { + onFocusReturnRef.current(); + } else { + focusedBeforeMount.current.focus(); + } + } + }, [] ); +} + +export default useFocusReturn; diff --git a/packages/compose/src/hooks/use-media-query/index.js b/packages/compose/src/hooks/use-media-query/index.js index 63168c218a6e5f..8c0f0844d6b1c4 100644 --- a/packages/compose/src/hooks/use-media-query/index.js +++ b/packages/compose/src/hooks/use-media-query/index.js @@ -11,7 +11,12 @@ import { useState, useEffect } from '@wordpress/element'; */ export default function useMediaQuery( query ) { const [ match, setMatch ] = useState( - query && window.matchMedia( query ).matches + () => + !! ( + query && + typeof window !== 'undefined' && + window.matchMedia( query ).matches + ) ); useEffect( () => { diff --git a/packages/compose/src/hooks/use-throttle/index.js b/packages/compose/src/hooks/use-throttle/index.js new file mode 100644 index 00000000000000..e6436424d71212 --- /dev/null +++ b/packages/compose/src/hooks/use-throttle/index.js @@ -0,0 +1,26 @@ +/** + * External dependencies + */ +import { throttle } from 'lodash'; +import { useMemoOne } from 'use-memo-one'; + +/** + * WordPress dependencies + */ +import { useEffect } from '@wordpress/element'; + +/** + * Throttles a function with Lodash's `throttle`. A new throttled function will + * be returned and any scheduled calls cancelled if any of the arguments change, + * including the function to throttle, so please wrap functions created on + * render in components in `useCallback`. + * + * @param {...any} args Arguments passed to Lodash's `throttle`. + * + * @return {Function} Throttled function. + */ +export default function useThrottle( ...args ) { + const throttled = useMemoOne( () => throttle( ...args ), args ); + useEffect( () => () => throttled.cancel(), [ throttled ] ); + return throttled; +} diff --git a/packages/compose/src/index.js b/packages/compose/src/index.js index 3afcc9df90c585..df5362ee366038 100644 --- a/packages/compose/src/index.js +++ b/packages/compose/src/index.js @@ -13,8 +13,13 @@ export { default as withSafeTimeout } from './higher-order/with-safe-timeout'; export { default as withState } from './higher-order/with-state'; // Hooks +export { default as useConstrainedTabbing } from './hooks/use-constrained-tabbing'; export { default as useCopyOnClick } from './hooks/use-copy-on-click'; +export { default as __experimentalUseDialog } from './hooks/use-dialog'; export { default as __experimentalUseDragging } from './hooks/use-dragging'; +export { default as useFocusOnMount } from './hooks/use-focus-on-mount'; +export { default as __experimentalUseFocusOutside } from './hooks/use-focus-outside'; +export { default as useFocusReturn } from './hooks/use-focus-return'; export { default as useInstanceId } from './hooks/use-instance-id'; export { default as useKeyboardShortcut } from './hooks/use-keyboard-shortcut'; export { default as useMediaQuery } from './hooks/use-media-query'; @@ -25,3 +30,4 @@ export { default as useResizeObserver } from './hooks/use-resize-observer'; export { default as useAsyncList } from './hooks/use-async-list'; export { default as useWarnOnChange } from './hooks/use-warn-on-change'; export { default as useDebounce } from './hooks/use-debounce'; +export { default as useThrottle } from './hooks/use-throttle'; diff --git a/packages/compose/src/index.native.js b/packages/compose/src/index.native.js index 53abcc48aca482..04334739cd4e1f 100644 --- a/packages/compose/src/index.native.js +++ b/packages/compose/src/index.native.js @@ -13,7 +13,9 @@ export { default as withSafeTimeout } from './higher-order/with-safe-timeout'; export { default as withState } from './higher-order/with-state'; // Hooks +export { default as useConstrainedTabbing } from './hooks/use-constrained-tabbing'; export { default as __experimentalUseDragging } from './hooks/use-dragging'; +export { default as __experimentalUseFocusOutside } from './hooks/use-focus-outside'; export { default as useInstanceId } from './hooks/use-instance-id'; export { default as useKeyboardShortcut } from './hooks/use-keyboard-shortcut'; export { default as useMediaQuery } from './hooks/use-media-query'; diff --git a/packages/core-data/CHANGELOG.md b/packages/core-data/CHANGELOG.md index 92ed7e8974b671..43d507660c82b5 100644 --- a/packages/core-data/CHANGELOG.md +++ b/packages/core-data/CHANGELOG.md @@ -2,26 +2,30 @@ ## Unreleased +### New Feature + +- Added a store definition `store` for the core data namespace to use with `@wordpress/data` API ([#26655](https://github.com/WordPress/gutenberg/pull/26655)). + ## 2.21.0 (2020-09-03) ### New Feature -- The `deleteEntityRecord` and `removeItems` actions have been added. -- The `isDeletingEntityRecord` and `getLastEntityDeleteError` selectors have been added. -- A `delete<entity.name>` helper is created for every registered entity. +- The `deleteEntityRecord` and `removeItems` actions have been added. +- The `isDeletingEntityRecord` and `getLastEntityDeleteError` selectors have been added. +- A `delete<entity.name>` helper is created for every registered entity. ## 2.3.0 (2019-05-21) ### New features -- The `getAutosave`, `getAutosaves` and `getCurrentUser` selectors have been added. -- The `receiveAutosaves` and `receiveCurrentUser` actions have been added. +- The `getAutosave`, `getAutosaves` and `getCurrentUser` selectors have been added. +- The `receiveAutosaves` and `receiveCurrentUser` actions have been added. ## 2.0.16 (2019-01-03) ### Bug Fixes -- Fixed the `hasUploadPermissions` selector to always return a boolean. Previously, it may have returned an empty object. This should have no impact for most consumers, assuming usage as a [truthy value](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) in conditions. +- Fixed the `hasUploadPermissions` selector to always return a boolean. Previously, it may have returned an empty object. This should have no impact for most consumers, assuming usage as a [truthy value](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) in conditions. ## 2.0.15 (2018-12-12) @@ -49,10 +53,10 @@ ### Breaking Change -- `dispatch("core").receiveTerms` has been deprecated. Please use `dispatch("core").receiveEntityRecords` instead. -- `getCategories` resolvers has been deprecated. Please use `getEntityRecords` resolver instead. -- `select("core").getTerms` has been deprecated. Please use `select("core").getEntityRecords` instead. -- `select("core").getCategories` has been deprecated. Please use `select("core").getEntityRecords` instead. -- `wp.data.select("core").isRequestingCategories` has been deprecated. Please use `wp.data.select("core/data").isResolving` instead. -- `select("core").isRequestingTerms` has been deprecated. Please use `select("core").isResolving` instead. -- Change how required built-ins are polyfilled with Babel 7 ([#9171](https://github.com/WordPress/gutenberg/pull/9171)). If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. +- `dispatch("core").receiveTerms` has been deprecated. Please use `dispatch("core").receiveEntityRecords` instead. +- `getCategories` resolvers has been deprecated. Please use `getEntityRecords` resolver instead. +- `select("core").getTerms` has been deprecated. Please use `select("core").getEntityRecords` instead. +- `select("core").getCategories` has been deprecated. Please use `select("core").getEntityRecords` instead. +- `wp.data.select("core").isRequestingCategories` has been deprecated. Please use `wp.data.select("core/data").isResolving` instead. +- `select("core").isRequestingTerms` has been deprecated. Please use `select("core").isResolving` instead. +- Change how required built-ins are polyfilled with Babel 7 ([#9171](https://github.com/WordPress/gutenberg/pull/9171)). If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. diff --git a/packages/core-data/README.md b/packages/core-data/README.md index fdb7e87043ac20..bc887ad571c7cd 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -271,6 +271,7 @@ Returns all available authors. _Parameters_ - _state_ `Object`: Data state. +- _query_ `(Object|undefined)`: Optional object of query parameters to include with request. _Returns_ diff --git a/packages/core-data/package.json b/packages/core-data/package.json index fd6534c182d9ab..2d8880e230dd9c 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-data", - "version": "2.24.1", + "version": "2.24.2", "description": "Access to and manipulation of core WordPress entities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -23,10 +23,10 @@ "module": "build-module/index.js", "react-native": "src/index", "sideEffects": [ - "(src|build|build-module)/index.js" + "{src,build,build-module}/index.js" ], "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/blocks": "file:../blocks", "@wordpress/data": "file:../data", @@ -38,7 +38,8 @@ "@wordpress/url": "file:../url", "equivalent-key-map": "^0.2.2", "lodash": "^4.17.19", - "rememo": "^3.0.0" + "rememo": "^3.0.0", + "uuid": "^8.3.0" }, "publishConfig": { "access": "public" diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 7356d1ef0bb561..03a675531cbc04 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -2,11 +2,13 @@ * External dependencies */ import { castArray, get, isEqual, find } from 'lodash'; +import { v4 as uuid } from 'uuid'; /** * WordPress dependencies */ -import { apiFetch, syncSelect } from '@wordpress/data-controls'; +import { controls } from '@wordpress/data'; +import { apiFetch } from '@wordpress/data-controls'; import { addQueryArgs } from '@wordpress/url'; /** @@ -14,6 +16,10 @@ import { addQueryArgs } from '@wordpress/url'; */ import { receiveItems, removeItems, receiveQueriedItems } from './queried-data'; import { getKindEntities, DEFAULT_ENTITY_KEY } from './entities'; +import { + __unstableAcquireStoreLock, + __unstableReleaseStoreLock, +} from './locks'; /** * Returns an action object used in signalling that authors have been received. @@ -161,39 +167,48 @@ export function* deleteEntityRecord( kind, name, recordId, query ) { return; } - yield { - type: 'DELETE_ENTITY_RECORD_START', - kind, - name, - recordId, - }; - + const lock = yield* __unstableAcquireStoreLock( + 'core', + [ 'entities', 'data', kind, name, recordId ], + { exclusive: true } + ); try { - let path = `${ entity.baseURL }/${ recordId }`; + yield { + type: 'DELETE_ENTITY_RECORD_START', + kind, + name, + recordId, + }; - if ( query ) { - path = addQueryArgs( path, query ); - } + try { + let path = `${ entity.baseURL }/${ recordId }`; - deletedRecord = yield apiFetch( { - path, - method: 'DELETE', - } ); + if ( query ) { + path = addQueryArgs( path, query ); + } - yield removeItems( kind, name, recordId, true ); - } catch ( _error ) { - error = _error; - } + deletedRecord = yield apiFetch( { + path, + method: 'DELETE', + } ); - yield { - type: 'DELETE_ENTITY_RECORD_FINISH', - kind, - name, - recordId, - error, - }; + yield removeItems( kind, name, recordId, true ); + } catch ( _error ) { + error = _error; + } - return deletedRecord; + yield { + type: 'DELETE_ENTITY_RECORD_FINISH', + kind, + name, + recordId, + error, + }; + + return deletedRecord; + } finally { + yield* __unstableReleaseStoreLock( lock ); + } } /** @@ -210,21 +225,21 @@ export function* deleteEntityRecord( kind, name, recordId, query ) { * @return {Object} Action object. */ export function* editEntityRecord( kind, name, recordId, edits, options = {} ) { - const entity = yield syncSelect( 'core', 'getEntity', kind, name ); + const entity = yield controls.select( 'core', 'getEntity', kind, name ); if ( ! entity ) { throw new Error( `The entity being edited (${ kind }, ${ name }) does not have a loaded config.` ); } const { transientEdits = {}, mergedEdits = {} } = entity; - const record = yield syncSelect( + const record = yield controls.select( 'core', 'getRawEntityRecord', kind, name, recordId ); - const editedRecord = yield syncSelect( + const editedRecord = yield controls.select( 'core', 'getEditedEntityRecord', kind, @@ -270,7 +285,7 @@ export function* editEntityRecord( kind, name, recordId, edits, options = {} ) { * an entity record, if any. */ export function* undo() { - const undoEdit = yield syncSelect( 'core', 'getUndoEdit' ); + const undoEdit = yield controls.select( 'core', 'getUndoEdit' ); if ( ! undoEdit ) { return; } @@ -288,7 +303,7 @@ export function* undo() { * edit to an entity record, if any. */ export function* redo() { - const redoEdit = yield syncSelect( 'core', 'getRedoEdit' ); + const redoEdit = yield controls.select( 'core', 'getRedoEdit' ); if ( ! redoEdit ) { return; } @@ -333,228 +348,252 @@ export function* saveEntityRecord( const entityIdKey = entity.key || DEFAULT_ENTITY_KEY; const recordId = record[ entityIdKey ]; - // Evaluate optimized edits. - // (Function edits that should be evaluated on save to avoid expensive computations on every edit.) - for ( const [ key, value ] of Object.entries( record ) ) { - if ( typeof value === 'function' ) { - const evaluatedValue = value( - yield syncSelect( - 'core', - 'getEditedEntityRecord', + const lock = yield* __unstableAcquireStoreLock( + 'core', + [ 'entities', 'data', kind, name, recordId || uuid() ], + { exclusive: true } + ); + try { + // Evaluate optimized edits. + // (Function edits that should be evaluated on save to avoid expensive computations on every edit.) + for ( const [ key, value ] of Object.entries( record ) ) { + if ( typeof value === 'function' ) { + const evaluatedValue = value( + yield controls.select( + 'core', + 'getEditedEntityRecord', + kind, + name, + recordId + ) + ); + yield editEntityRecord( kind, name, - recordId - ) - ); - yield editEntityRecord( - kind, - name, - recordId, - { - [ key ]: evaluatedValue, - }, - { undoIgnore: true } - ); - record[ key ] = evaluatedValue; + recordId, + { + [ key ]: evaluatedValue, + }, + { undoIgnore: true } + ); + record[ key ] = evaluatedValue; + } } - } - yield { - type: 'SAVE_ENTITY_RECORD_START', - kind, - name, - recordId, - isAutosave, - }; - let updatedRecord; - let error; - let persistedEntity; - let currentEdits; - try { - const path = `${ entity.baseURL }${ recordId ? '/' + recordId : '' }`; - const persistedRecord = yield syncSelect( - 'core', - 'getRawEntityRecord', + yield { + type: 'SAVE_ENTITY_RECORD_START', kind, name, - recordId - ); - - if ( isAutosave ) { - // Most of this autosave logic is very specific to posts. - // This is fine for now as it is the only supported autosave, - // but ideally this should all be handled in the back end, - // so the client just sends and receives objects. - const currentUser = yield syncSelect( 'core', 'getCurrentUser' ); - const currentUserId = currentUser ? currentUser.id : undefined; - const autosavePost = yield syncSelect( + recordId, + isAutosave, + }; + let updatedRecord; + let error; + let persistedEntity; + let currentEdits; + try { + const path = `${ entity.baseURL }${ + recordId ? '/' + recordId : '' + }`; + const persistedRecord = yield controls.select( 'core', - 'getAutosave', - persistedRecord.type, - persistedRecord.id, - currentUserId - ); - // Autosaves need all expected fields to be present. - // So we fallback to the previous autosave and then - // to the actual persisted entity if the edits don't - // have a value. - let data = { ...persistedRecord, ...autosavePost, ...record }; - data = Object.keys( data ).reduce( - ( acc, key ) => { - if ( [ 'title', 'excerpt', 'content' ].includes( key ) ) { - // Edits should be the "raw" attribute values. - acc[ key ] = get( data[ key ], 'raw', data[ key ] ); - } - return acc; - }, - { status: data.status === 'auto-draft' ? 'draft' : data.status } + 'getRawEntityRecord', + kind, + name, + recordId ); - updatedRecord = yield apiFetch( { - path: `${ path }/autosaves`, - method: 'POST', - data, - } ); - // An autosave may be processed by the server as a regular save - // when its update is requested by the author and the post had - // draft or auto-draft status. - if ( persistedRecord.id === updatedRecord.id ) { - let newRecord = { - ...persistedRecord, - ...data, - ...updatedRecord, - }; - newRecord = Object.keys( newRecord ).reduce( ( acc, key ) => { - // These properties are persisted in autosaves. - if ( [ 'title', 'excerpt', 'content' ].includes( key ) ) { - // Edits should be the "raw" attribute values. - acc[ key ] = get( - newRecord[ key ], - 'raw', - newRecord[ key ] - ); - } else if ( key === 'status' ) { - // Status is only persisted in autosaves when going from - // "auto-draft" to "draft". - acc[ key ] = - persistedRecord.status === 'auto-draft' && - newRecord.status === 'draft' - ? newRecord.status - : persistedRecord.status; - } else { - // These properties are not persisted in autosaves. - acc[ key ] = get( - persistedRecord[ key ], - 'raw', - persistedRecord[ key ] - ); + + if ( isAutosave ) { + // Most of this autosave logic is very specific to posts. + // This is fine for now as it is the only supported autosave, + // but ideally this should all be handled in the back end, + // so the client just sends and receives objects. + const currentUser = yield controls.select( + 'core', + 'getCurrentUser' + ); + const currentUserId = currentUser ? currentUser.id : undefined; + const autosavePost = yield controls.select( + 'core', + 'getAutosave', + persistedRecord.type, + persistedRecord.id, + currentUserId + ); + // Autosaves need all expected fields to be present. + // So we fallback to the previous autosave and then + // to the actual persisted entity if the edits don't + // have a value. + let data = { ...persistedRecord, ...autosavePost, ...record }; + data = Object.keys( data ).reduce( + ( acc, key ) => { + if ( + [ 'title', 'excerpt', 'content' ].includes( key ) + ) { + // Edits should be the "raw" attribute values. + acc[ key ] = get( data[ key ], 'raw', data[ key ] ); + } + return acc; + }, + { + status: + data.status === 'auto-draft' + ? 'draft' + : data.status, } - return acc; - }, {} ); + ); + updatedRecord = yield apiFetch( { + path: `${ path }/autosaves`, + method: 'POST', + data, + } ); + // An autosave may be processed by the server as a regular save + // when its update is requested by the author and the post had + // draft or auto-draft status. + if ( persistedRecord.id === updatedRecord.id ) { + let newRecord = { + ...persistedRecord, + ...data, + ...updatedRecord, + }; + newRecord = Object.keys( newRecord ).reduce( + ( acc, key ) => { + // These properties are persisted in autosaves. + if ( + [ 'title', 'excerpt', 'content' ].includes( + key + ) + ) { + // Edits should be the "raw" attribute values. + acc[ key ] = get( + newRecord[ key ], + 'raw', + newRecord[ key ] + ); + } else if ( key === 'status' ) { + // Status is only persisted in autosaves when going from + // "auto-draft" to "draft". + acc[ key ] = + persistedRecord.status === 'auto-draft' && + newRecord.status === 'draft' + ? newRecord.status + : persistedRecord.status; + } else { + // These properties are not persisted in autosaves. + acc[ key ] = get( + persistedRecord[ key ], + 'raw', + persistedRecord[ key ] + ); + } + return acc; + }, + {} + ); + yield receiveEntityRecords( + kind, + name, + newRecord, + undefined, + true + ); + } else { + yield receiveAutosaves( persistedRecord.id, updatedRecord ); + } + } else { + let edits = record; + if ( entity.__unstablePrePersist ) { + edits = { + ...edits, + ...entity.__unstablePrePersist( + persistedRecord, + edits + ), + }; + } + + // Get the full local version of the record before the update, + // to merge it with the edits and then propagate it to subscribers + persistedEntity = yield controls.select( + 'core', + '__experimentalGetEntityRecordNoResolver', + kind, + name, + recordId + ); + currentEdits = yield controls.select( + 'core', + 'getEntityRecordEdits', + kind, + name, + recordId + ); + yield receiveEntityRecords( + kind, + name, + { ...persistedEntity, ...edits }, + undefined, + // This must be false or it will trigger a GET request in parallel to the PUT/POST below + false + ); + + updatedRecord = yield apiFetch( { + path, + method: recordId ? 'PUT' : 'POST', + data: edits, + } ); yield receiveEntityRecords( kind, name, - newRecord, + updatedRecord, undefined, true ); - } else { - yield receiveAutosaves( persistedRecord.id, updatedRecord ); } - } else { - // Auto drafts should be converted to drafts on explicit saves and we should not respect their default title, - // but some plugins break with this behavior so we can't filter it on the server. - let data = record; - if ( - kind === 'postType' && - persistedRecord && - persistedRecord.status === 'auto-draft' - ) { - if ( ! data.status ) { - data = { ...data, status: 'draft' }; - } - if ( ! data.title || data.title === 'Auto Draft' ) { - data = { ...data, title: '' }; - } - } - - // Get the full local version of the record before the update, - // to merge it with the edits and then propagate it to subscribers - persistedEntity = yield syncSelect( - 'core', - '__experimentalGetEntityRecordNoResolver', - kind, - name, - recordId - ); - currentEdits = yield syncSelect( - 'core', - 'getEntityRecordEdits', - kind, - name, - recordId - ); - yield receiveEntityRecords( - kind, - name, - { ...persistedEntity, ...data }, - undefined, - true - ); + } catch ( _error ) { + error = _error; - updatedRecord = yield apiFetch( { - path, - method: recordId ? 'PUT' : 'POST', - data, - } ); - yield receiveEntityRecords( - kind, - name, - updatedRecord, - undefined, - true - ); - } - } catch ( _error ) { - error = _error; - - // If we got to the point in the try block where we made an optimistic update, - // we need to roll it back here. - if ( persistedEntity && currentEdits ) { - yield receiveEntityRecords( - kind, - name, - persistedEntity, - undefined, - true - ); - yield editEntityRecord( - kind, - name, - recordId, - { - ...currentEdits, - ...( yield syncSelect( - 'core', - 'getEntityRecordEdits', - kind, - name, - recordId - ) ), - }, - { undoIgnore: true } - ); + // If we got to the point in the try block where we made an optimistic update, + // we need to roll it back here. + if ( persistedEntity && currentEdits ) { + yield receiveEntityRecords( + kind, + name, + persistedEntity, + undefined, + true + ); + yield editEntityRecord( + kind, + name, + recordId, + { + ...currentEdits, + ...( yield controls.select( + 'core', + 'getEntityRecordEdits', + kind, + name, + recordId + ) ), + }, + { undoIgnore: true } + ); + } } + yield { + type: 'SAVE_ENTITY_RECORD_FINISH', + kind, + name, + recordId, + error, + isAutosave, + }; + + return updatedRecord; + } finally { + yield* __unstableReleaseStoreLock( lock ); } - yield { - type: 'SAVE_ENTITY_RECORD_FINISH', - kind, - name, - recordId, - error, - isAutosave, - }; - - return updatedRecord; } /** @@ -567,7 +606,7 @@ export function* saveEntityRecord( */ export function* saveEditedEntityRecord( kind, name, recordId, options ) { if ( - ! ( yield syncSelect( + ! ( yield controls.select( 'core', 'hasEditsForEntityRecord', kind, @@ -577,7 +616,7 @@ export function* saveEditedEntityRecord( kind, name, recordId, options ) { ) { return; } - const edits = yield syncSelect( + const edits = yield controls.select( 'core', 'getEntityRecordNonTransientEdits', kind, diff --git a/packages/core-data/src/controls.js b/packages/core-data/src/controls.js new file mode 100644 index 00000000000000..e1e069a8e03062 --- /dev/null +++ b/packages/core-data/src/controls.js @@ -0,0 +1,17 @@ +export function regularFetch( url ) { + return { + type: 'REGULAR_FETCH', + url, + }; +} +const controls = { + async REGULAR_FETCH( { url } ) { + const { data } = await window + .fetch( url ) + .then( ( res ) => res.json() ); + + return data; + }, +}; + +export default controls; diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index 9dfd5071404921..c3c81b8dbfee03 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -6,7 +6,8 @@ import { upperFirst, camelCase, map, find, get, startCase } from 'lodash'; /** * WordPress dependencies */ -import { apiFetch, syncSelect } from '@wordpress/data-controls'; +import { controls } from '@wordpress/data'; +import { apiFetch } from '@wordpress/data-controls'; import { __ } from '@wordpress/i18n'; /** @@ -113,6 +114,69 @@ export const kinds = [ { name: 'taxonomy', loadEntities: loadTaxonomyEntities }, ]; +/** + * Returns a function to be used to retrieve the title of a given post type record. + * + * @param {string} postTypeName PostType name. + * @return {Function} getTitle. + */ +export const getPostTypeTitle = ( postTypeName ) => ( record ) => { + if ( [ 'wp_template_part', 'wp_template' ].includes( postTypeName ) ) { + return ( + record?.title?.rendered || record?.title || startCase( record.slug ) + ); + } + return record?.title?.rendered || record?.title || String( record.id ); +}; + +/** + * Returns a function to be used to retrieve extra edits to apply before persisting a post type. + * + * @param {string} postTypeName PostType name. + * @return {Function} prePersistHandler. + */ +export const getPostTypePrePersistHandler = ( postTypeName ) => ( + persistedRecord, + edits +) => { + const newEdits = {}; + + // Fix template titles. + if ( + [ 'wp_template', 'wp_template_part' ].includes( postTypeName ) && + ! edits.title && + ! persistedRecord.title + ) { + newEdits.title = persistedRecord + ? getPostTypeTitle( postTypeName )( persistedRecord ) + : edits.slug; + } + + // Templates and template parts can only be published. + if ( [ 'wp_template', 'wp_template_part' ].includes( postTypeName ) ) { + newEdits.status = 'publish'; + } + + if ( persistedRecord?.status === 'auto-draft' ) { + // Saving an auto-draft should create a draft by default. + if ( ! edits.status && ! newEdits.status ) { + newEdits.status = 'draft'; + } + + // Fix the auto-draft default title. + if ( + ( ! edits.title || edits.title === 'Auto Draft' ) && + ! newEdits.title && + ( ! persistedRecord?.title || + persistedRecord?.title === 'Auto Draft' ) + ) { + newEdits.title = ''; + } + } + + return newEdits; +}; + /** * Returns the list of post type entities. * @@ -132,12 +196,8 @@ function* loadPostTypeEntities() { selectionEnd: true, }, mergedEdits: { meta: true }, - getTitle( record ) { - if ( name === 'wp_template_part' || name === 'wp_template' ) { - return startCase( record.slug ); - } - return get( record, [ 'title', 'rendered' ], record.id ); - }, + getTitle: getPostTypeTitle( name ), + __unstablePrePersist: getPostTypePrePersistHandler( name ), }; } ); } @@ -196,7 +256,7 @@ export const getMethodName = ( * @return {Array} Entities */ export function* getKindEntities( kind ) { - let entities = yield syncSelect( 'core', 'getEntitiesByKind', kind ); + let entities = yield controls.select( 'core', 'getEntitiesByKind', kind ); if ( entities && entities.length !== 0 ) { return entities; } diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index 06a74cbab4209d..c59d5bcf97ce35 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { registerStore } from '@wordpress/data'; +import { createReduxStore, register } from '@wordpress/data'; import { controls } from '@wordpress/data-controls'; /** @@ -11,8 +11,11 @@ import reducer from './reducer'; import * as selectors from './selectors'; import * as actions from './actions'; import * as resolvers from './resolvers'; +import * as locksSelectors from './locks/selectors'; +import * as locksActions from './locks/actions'; +import customControls from './controls'; import { defaultEntities, getMethodName } from './entities'; -import { REDUCER_KEY } from './name'; +import { STORE_NAME } from './name'; // The entity selectors/resolvers and actions are shortcuts to their generic equivalents // (getEntityRecord, getEntityRecords, updateEntityRecord, updateEntityRecordss) @@ -54,13 +57,24 @@ const entityActions = defaultEntities.reduce( ( result, entity ) => { return result; }, {} ); -registerStore( REDUCER_KEY, { +const storeConfig = { reducer, - controls, - actions: { ...actions, ...entityActions }, - selectors: { ...selectors, ...entitySelectors }, + controls: { ...customControls, ...controls }, + actions: { ...actions, ...entityActions, ...locksActions }, + selectors: { ...selectors, ...entitySelectors, ...locksSelectors }, resolvers: { ...resolvers, ...entityResolvers }, -} ); +}; + +/** + * Store definition for the code data namespace. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore + * + * @type {Object} + */ +export const store = createReduxStore( STORE_NAME, storeConfig ); + +register( store ); export { default as EntityProvider } from './entity-provider'; export * from './entity-provider'; diff --git a/packages/core-data/src/locks/README.md b/packages/core-data/src/locks/README.md new file mode 100644 index 00000000000000..b0a6cb57d9d343 --- /dev/null +++ b/packages/core-data/src/locks/README.md @@ -0,0 +1,108 @@ +# Store locks + +Operations mutating the store (resolvers, saveEntityRecord) interfere with each other by changing the state used by other "concurrent" operations. The solution is to enable these operations to lock parts of the state tree that are expected to remain constant for the duration of the operation. For example `saveEntityRecord("postType", "book", 3)` would acquire a lock on a book with id=3 and nothing else. + +More generally, interfering store operations should run sequentially. Each operation acquires a lock scoped to its state dependencies. If a lock cannot be acquired, the operation is delayed until a lock can be acquired. Locks are either exclusive or shared. If all operations request a shared lock, everything is executed concurrently. If any operation requests an exclusive lock, is it run only after all other locks (shared or exclusive) acting on the same scope are released. + +The most complex part of this API is the concept of lock scope. Locks are stored in a tree. Each node in the tree looks like this: + +```jsx +{ + "locks": [], + "children": {} +} +``` + +Locks are stored like this: + +```jsx +{ + "locks": [ { "exclusive": false, /* data */ } ], + "children": {} +} +``` + +A more complete, but still simplified, tree looks like this: + +```jsx +{ + "locks": [], + "children": { + "book": { + "locks": [], + "children": { + 1: { + "locks": [], + "children": {} + }, + 2: { + "locks": [], + "children": {} + } + } + } + } +} +``` + +Let's imagine we want to fetch a list of books. One way to control concurrency would be to acquire a shared lock on `book`. The lock will be granted once there are **no exclusive locks** on: +* `book` itself +* All its parents (`root`) +* All its descendants (`book > 1`, `book > 2`) + +In the case above we're good to grab the lock, let's do it then: + +```jsx +{ + "locks": [], + "children": { + "book": { + "locks": [ { "exclusive": false, /* data */ } ], + "children": { + 1: { + "locks": [], + "children": {} + }, + 2: { + "locks": [], + "children": {} + } + } + } + } +} +``` + +Let's imagine that another fetch was triggered to get a filtered list of entities. By checking the criteria above, it's okay to grab a shared lock on books so now we have two: +```jsx +/* ... */ + "book": { + "locks": [ { "exclusive": false, /* data */ }, { "exclusive": false, /* data */ } ], + } +/* ... */ +``` + +While these are running, the user triggered an update of book id=1. Now an update operation requests an exclusive lock. It will be able to acquire one once there are **no locks at all** on: +* `book > 1` itself +* All its parents (`root`, `book`) +* All its descendants (an empty list in this case) + +Since there are two shared locks on `book`, the operation is delayed until both of them are released. Once that happens, an exclusive lock is granted on a specific book: + +```jsx +/* ... */ + "book": { + "locks": [], + "children": { + 1: { + "locks": [ { "exclusive": true, /* data */ } ], + "children": {} + }, + }, + }, +/* ... */ +``` + +If any fetch operation is triggered now, it will attempt to acquire a shared lock on `book`. Since one of `book` descendants holds an exclusive lock, fetch must wait until that lock is released. + +This structure makes it quite easy to control concurrent reads/writes with any granularity. For example, if we wanted to make sure two fetch operations may run only if their `query` is different, each could acquire two locks: a shared lock on `book` to prevent clashing with writes, and an exclusive lock on an unrelated branch such as `fetchByQuery > [query]`. diff --git a/packages/core-data/src/locks/actions.js b/packages/core-data/src/locks/actions.js new file mode 100644 index 00000000000000..2689b35bdf0a7b --- /dev/null +++ b/packages/core-data/src/locks/actions.js @@ -0,0 +1,64 @@ +/** + * WordPress dependencies + */ +import { __unstableAwaitPromise } from '@wordpress/data-controls'; +import { controls } from '@wordpress/data'; + +export function* __unstableAcquireStoreLock( store, path, { exclusive } ) { + const promise = yield* __unstableEnqueueLockRequest( store, path, { + exclusive, + } ); + yield* __unstableProcessPendingLockRequests(); + return yield __unstableAwaitPromise( promise ); +} + +export function* __unstableEnqueueLockRequest( store, path, { exclusive } ) { + let notifyAcquired; + const promise = new Promise( ( resolve ) => { + notifyAcquired = resolve; + } ); + yield { + type: 'ENQUEUE_LOCK_REQUEST', + request: { store, path, exclusive, notifyAcquired }, + }; + return promise; +} + +export function* __unstableReleaseStoreLock( lock ) { + yield { + type: 'RELEASE_LOCK', + lock, + }; + yield* __unstableProcessPendingLockRequests(); +} + +export function* __unstableProcessPendingLockRequests() { + yield { + type: 'PROCESS_PENDING_LOCK_REQUESTS', + }; + const lockRequests = yield controls.select( + 'core', + '__unstableGetPendingLockRequests' + ); + for ( const request of lockRequests ) { + const { store, path, exclusive, notifyAcquired } = request; + const isAvailable = yield controls.select( + 'core', + '__unstableIsLockAvailable', + store, + path, + { + exclusive, + } + ); + if ( isAvailable ) { + const lock = { store, path, exclusive }; + yield { + type: 'GRANT_LOCK_REQUEST', + lock, + request, + }; + notifyAcquired( lock ); + } + } +} diff --git a/packages/core-data/src/locks/index.js b/packages/core-data/src/locks/index.js new file mode 100644 index 00000000000000..57e124e445d87d --- /dev/null +++ b/packages/core-data/src/locks/index.js @@ -0,0 +1,3 @@ +export * from './actions'; +export * from './selectors'; +export { default as reducer } from './reducer'; diff --git a/packages/core-data/src/locks/reducer.js b/packages/core-data/src/locks/reducer.js new file mode 100644 index 00000000000000..6a29de6a3ffaba --- /dev/null +++ b/packages/core-data/src/locks/reducer.js @@ -0,0 +1,64 @@ +/** + * Internal dependencies + */ +import { getNode, deepCopyLocksTreePath } from './utils'; + +const DEFAULT_STATE = { + requests: [], + tree: { + locks: [], + children: {}, + }, +}; + +/** + * Reducer returning locks. + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +export function locks( state = DEFAULT_STATE, action ) { + switch ( action.type ) { + case 'ENQUEUE_LOCK_REQUEST': { + const { request } = action; + return { + ...state, + requests: [ request, ...state.requests ], + }; + } + case 'GRANT_LOCK_REQUEST': { + const { lock, request } = action; + const { store, path } = request; + const storePath = [ store, ...path ]; + + const newTree = deepCopyLocksTreePath( state.tree, storePath ); + const node = getNode( newTree, storePath ); + node.locks = [ ...node.locks, lock ]; + + return { + ...state, + requests: state.requests.filter( ( r ) => r !== request ), + tree: newTree, + }; + } + case 'RELEASE_LOCK': { + const { lock } = action; + const storePath = [ lock.store, ...lock.path ]; + + const newTree = deepCopyLocksTreePath( state.tree, storePath ); + const node = getNode( newTree, storePath ); + node.locks = node.locks.filter( ( l ) => l !== lock ); + + return { + ...state, + tree: newTree, + }; + } + } + + return state; +} + +export default locks; diff --git a/packages/core-data/src/locks/selectors.js b/packages/core-data/src/locks/selectors.js new file mode 100644 index 00000000000000..233b36985a5c54 --- /dev/null +++ b/packages/core-data/src/locks/selectors.js @@ -0,0 +1,41 @@ +/** + * Internal dependencies + */ +import { + iterateDescendants, + iteratePath, + hasConflictingLock, + getNode, +} from './utils'; + +export function __unstableGetPendingLockRequests( state ) { + return state.locks.requests; +} + +export function __unstableIsLockAvailable( state, store, path, { exclusive } ) { + const storePath = [ store, ...path ]; + const locks = state.locks.tree; + + // Validate all parents and the node itself + for ( const node of iteratePath( locks, storePath ) ) { + if ( hasConflictingLock( { exclusive }, node.locks ) ) { + return false; + } + } + + // iteratePath terminates early if path is unreachable, let's + // re-fetch the node and check it exists in the tree. + const node = getNode( locks, storePath ); + if ( ! node ) { + return true; + } + + // Validate all nested nodes + for ( const descendant of iterateDescendants( node ) ) { + if ( hasConflictingLock( { exclusive }, descendant.locks ) ) { + return false; + } + } + + return true; +} diff --git a/packages/core-data/src/locks/test/actions.js b/packages/core-data/src/locks/test/actions.js new file mode 100644 index 00000000000000..f3d2cbdb1b44a1 --- /dev/null +++ b/packages/core-data/src/locks/test/actions.js @@ -0,0 +1,307 @@ +/** + * Internal dependencies + */ +import { + __unstableAcquireStoreLock, + __unstableEnqueueLockRequest, + __unstableReleaseStoreLock, + __unstableProcessPendingLockRequests, +} from '../actions'; + +const store = 'test'; +const path = [ 'blue', 'bird' ]; + +describe( '__unstableEnqueueLockRequest', () => { + it( 'Enqueues a lock request', async () => { + const fulfillment = __unstableEnqueueLockRequest( store, path, { + exclusive: true, + } ); + + // Start + expect( fulfillment.next().value ).toMatchObject( { + type: 'ENQUEUE_LOCK_REQUEST', + request: { + store, + path, + exclusive: true, + notifyAcquired: expect.any( Function ), + }, + } ); + + // Should return a promise + expect( fulfillment.next() ).toMatchObject( { + done: true, + value: expect.any( Promise ), + } ); + } ); + + it( 'Returns a promise fulfilled only after calling notifyAcquired', async () => { + const fulfillment = __unstableEnqueueLockRequest( store, path, { + exclusive: true, + } ); + const { request } = fulfillment.next().value; + const promise = fulfillment.next().value; + const fulfilled = jest.fn(); + promise.then( fulfilled ); + // Fulfilled should not be called until notifyAcquired is called + await sleep( 1 ); + expect( fulfilled ).not.toBeCalled(); + const lock = {}; + request.notifyAcquired( lock ); + expect( fulfilled ).not.toBeCalled(); + + // Promises are resolved only the next tick, so let's wait a little: + await sleep( 1 ); + expect( fulfilled ).toBeCalledTimes( 1 ); + + // Calling notifyAcquired again shouldn't have any effect: + request.notifyAcquired( lock ); + await sleep( 1 ); + expect( fulfilled ).toBeCalledTimes( 1 ); + } ); +} ); + +describe( '__unstableProcessPendingLockRequests', () => { + const exclusive = true; + const lock = { store, path, exclusive }; + + let notifyAcquired; + let request; + + beforeEach( () => { + notifyAcquired = jest.fn(); + request = { store, path, exclusive, notifyAcquired }; + } ); + + it( 'Grants a lock request that may be granted', async () => { + const fulfillment = __unstableProcessPendingLockRequests(); + + // Start + expect( fulfillment.next().value.type ).toBe( + 'PROCESS_PENDING_LOCK_REQUESTS' + ); + + // Get pending lock requests + expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' ); + + // Find one and check if the request may be granted + expect( fulfillment.next( [ request ] ).value.type ).toBe( + '@@data/SELECT' + ); + + // It may, grant it + expect( fulfillment.next( true ).value.type ).toBe( + 'GRANT_LOCK_REQUEST' + ); + + // Ensure the promise isn't fulfilled until after GRANT_LOCK_REQUEST finishes + expect( notifyAcquired ).not.toBeCalled(); + + // All requests processed, return + expect( fulfillment.next() ).toMatchObject( { + done: true, + value: undefined, + } ); + + // Ensure the promise is fulfilled once GRANT_LOCK_REQUEST finishes + expect( notifyAcquired ).toBeCalledWith( lock ); + expect( notifyAcquired ).toBeCalledTimes( 1 ); + + // All requests processed, return + expect( fulfillment.next() ).toMatchObject( { + done: true, + value: undefined, + } ); + } ); + + it( 'Does not grants a lock request that may not be granted', async () => { + const fulfillment = __unstableProcessPendingLockRequests(); + + // Start + expect( fulfillment.next().value.type ).toBe( + 'PROCESS_PENDING_LOCK_REQUESTS' + ); + + // Get pending lock requests + expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' ); + + // Find one and check if the request may be granted + expect( fulfillment.next( [ request ] ).value.type ).toBe( + '@@data/SELECT' + ); + + // It may not, let's finish + expect( fulfillment.next( false ) ).toMatchObject( { + done: true, + value: undefined, + } ); + } ); + + it( 'Handles multiple lock requests', async () => { + const fulfillment = __unstableProcessPendingLockRequests(); + + // Start + expect( fulfillment.next().value.type ).toBe( + 'PROCESS_PENDING_LOCK_REQUESTS' + ); + + // Get pending lock requests + expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' ); + + // Find one and check if the request may be granted + expect( fulfillment.next( [ request, request ] ).value.type ).toBe( + '@@data/SELECT' + ); + + // It may not, continue - check if the next one may be granted + expect( fulfillment.next( false ).value.type ).toBe( '@@data/SELECT' ); + // It may, grant it + expect( fulfillment.next( true ).value.type ).toBe( + 'GRANT_LOCK_REQUEST' + ); + + // Ensure the promise isn't fulfilled until after GRANT_LOCK_REQUEST finishes + expect( notifyAcquired ).not.toBeCalled(); + + // All requests processed, return + expect( fulfillment.next() ).toMatchObject( { + done: true, + value: undefined, + } ); + + // Ensure the promise is fulfilled once GRANT_LOCK_REQUEST finishes + expect( notifyAcquired ).toBeCalledWith( lock ); + expect( notifyAcquired ).toBeCalledTimes( 1 ); + + // All requests processed, return + expect( fulfillment.next() ).toMatchObject( { + done: true, + value: undefined, + } ); + } ); +} ); + +describe( '__unstableAcquireStoreLock', () => { + const exclusive = true; + const lock = { store, path, exclusive }; + + let notifyAcquired; + let request; + + beforeEach( () => { + notifyAcquired = jest.fn(); + request = { store, path, exclusive, notifyAcquired }; + } ); + + it( 'Enqueues a lock request and attempts to fulfill it', async () => { + const fulfillment = __unstableAcquireStoreLock( store, path, { + exclusive, + } ); + + // Start + expect( fulfillment.next().value.type ).toBe( 'ENQUEUE_LOCK_REQUEST' ); + + // Get pending lock requests + expect( fulfillment.next().value.type ).toBe( + 'PROCESS_PENDING_LOCK_REQUESTS' + ); + expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' ); + + // Check if lock may be granted + expect( fulfillment.next( [ request ] ).value.type ).toBe( + '@@data/SELECT' + ); + + // Grant lock request + expect( fulfillment.next( true ).value.type ).toBe( + 'GRANT_LOCK_REQUEST' + ); + + // Ensure the promise isn't fulfilled until after GRANT_LOCK_REQUEST finishes + expect( notifyAcquired ).not.toBeCalled(); + + // Await for lock promise fulfillment + expect( fulfillment.next( 'promise' ).value.type ).toBe( + 'AWAIT_PROMISE' + ); + + // Ensure the promise is fulfilled once GRANT_LOCK_REQUEST finishes + expect( notifyAcquired ).toBeCalledWith( lock ); + + // Return lock + expect( fulfillment.next( lock ) ).toMatchObject( { + done: true, + value: lock, + } ); + } ); + + it( 'Enqueues a lock request and waits until fultillment it when not available', async () => { + const fulfillment = __unstableAcquireStoreLock( store, path, { + exclusive, + } ); + + // Start + expect( fulfillment.next().value.type ).toBe( 'ENQUEUE_LOCK_REQUEST' ); + + // Get pending lock requests + expect( fulfillment.next().value.type ).toBe( + 'PROCESS_PENDING_LOCK_REQUESTS' + ); + expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' ); + + // Check if lock may be granted + expect( fulfillment.next( [ request ] ).value.type ).toBe( + '@@data/SELECT' + ); + + // Await until lock request is granted + expect( fulfillment.next( false ).value.type ).toBe( 'AWAIT_PROMISE' ); + + // Ensure the promise isn't fulfilled at this point... + expect( notifyAcquired ).not.toBeCalled(); + + await sleep( 1000 ); + + // ...or even a second lateer + expect( notifyAcquired ).not.toBeCalled(); + + // Let's assume the promise was fulfilled in the end, the action should return + // the lock once that happens. + expect( fulfillment.next( lock ) ).toMatchObject( { + done: true, + value: lock, + } ); + } ); +} ); + +describe( '__unstableReleaseStoreLock', () => { + const lock = { store, path, exclusive: true }; + + it( 'Releases a lock request and attempts to fulfill pending lock requests', async () => { + const fulfillment = __unstableReleaseStoreLock( lock ); + + // Start + expect( fulfillment.next().value ).toMatchObject( { + type: 'RELEASE_LOCK', + lock, + } ); + + // Attempt to grant any pending lock requests, find none, return + expect( fulfillment.next().value.type ).toBe( + 'PROCESS_PENDING_LOCK_REQUESTS' + ); + expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' ); + + // Short-circuit with no results and return + expect( fulfillment.next( [] ) ).toMatchObject( { + done: true, + value: undefined, + } ); + } ); +} ); + +const sleep = ( ms ) => { + const promise = new Promise( ( resolve ) => setTimeout( resolve, ms ) ); + jest.advanceTimersByTime( ms + 1 ); + return promise; +}; diff --git a/packages/core-data/src/locks/test/reducer.js b/packages/core-data/src/locks/test/reducer.js new file mode 100644 index 00000000000000..b422ae9aa5ffa2 --- /dev/null +++ b/packages/core-data/src/locks/test/reducer.js @@ -0,0 +1,131 @@ +/** + * External dependencies + */ +import deepFreeze from 'deep-freeze'; + +/** + * Internal dependencies + */ +import { locks } from '../reducer'; + +const buildNode = ( children = {} ) => ( { + locks: [], + children, +} ); + +describe( 'locks', () => { + it( 'Enqueues lock requests', () => { + const request = {}; + const state = deepFreeze( { + requests: [], + } ); + + expect( + locks( state, { + type: 'ENQUEUE_LOCK_REQUEST', + request, + } ) + ).toEqual( { + requests: [ request ], + } ); + } ); + + it( 'Grants lock requests', () => { + const red = buildNode(); + const blue = buildNode(); + const green = buildNode(); + const bird = buildNode( { red, blue, green } ); + const tree = buildNode( { bird } ); + + const request = { store: 'bird', path: [ 'green' ] }; + const state = deepFreeze( { + requests: [ request ], + tree, + } ); + + const lock = {}; + expect( + locks( state, { + type: 'GRANT_LOCK_REQUEST', + lock, + request, + } ) + ).toEqual( { + // Should remove the request... + requests: [], + tree: { + locks: [], + children: { + bird: { + locks: [], + children: { + red, + blue, + green: { + // ...and add the lock + locks: [ lock ], + children: {}, + }, + }, + }, + }, + }, + } ); + } ); + + it( 'Releases acquired locks', () => { + const red = buildNode(); + const blue = buildNode(); + const lock = { + store: 'bird', + path: [ 'green' ], + }; + const state = deepFreeze( { + // Should remove the request... + requests: [], + tree: { + locks: [], + children: { + bird: { + locks: [], + children: { + red, + blue, + green: { + // ...and add the lock + locks: [ lock ], + children: {}, + }, + }, + }, + }, + }, + } ); + + expect( + locks( state, { + type: 'RELEASE_LOCK', + lock, + } ) + ).toEqual( { + requests: [], + tree: { + locks: [], + children: { + bird: { + locks: [], + children: { + red, + blue, + green: { + // Should remove the lock + locks: [], + children: {}, + }, + }, + }, + }, + }, + } ); + } ); +} ); diff --git a/packages/core-data/src/locks/test/selectors.js b/packages/core-data/src/locks/test/selectors.js new file mode 100644 index 00000000000000..2f3f1cf77f707d --- /dev/null +++ b/packages/core-data/src/locks/test/selectors.js @@ -0,0 +1,473 @@ +/** + * External dependencies + */ +import deepFreeze from 'deep-freeze'; + +/** + * Internal dependencies + */ +import { + __unstableGetPendingLockRequests, + __unstableIsLockAvailable, +} from '../selectors'; +import { deepCopyLocksTreePath, getNode } from '../utils'; + +describe( '__unstableGetPendingLockRequests', () => { + it( 'returns pending lock requests', () => { + const state = deepFreeze( { + locks: { + requests: [ 1, 2, 3 ], + }, + } ); + + expect( __unstableGetPendingLockRequests( state ) ).toEqual( [ + 1, + 2, + 3, + ] ); + } ); +} ); + +describe( '__unstableIsLockAvailable', () => { + describe( 'smoke tests', () => { + it( 'returns true if lock is available', () => { + const state = deepFreeze( { + locks: { + tree: { + children: {}, + locks: [], + }, + }, + } ); + + expect( + __unstableIsLockAvailable( state, 'core', [], { + exclusive: true, + } ) + ).toBe( true ); + } ); + it( 'returns false if lock is not available', () => { + const state = deepFreeze( { + locks: { + tree: { + children: {}, + locks: [ { exclusive: false } ], + }, + }, + } ); + + expect( + __unstableIsLockAvailable( state, 'core', [], { + exclusive: true, + } ) + ).toBe( false ); + } ); + } ); + + describe( 'Advanced cases - exclusive lock', () => { + let state; + beforeEach( () => { + state = buildState( [ + [ 'core', 'entities', 'root', 'template_part', '3' ], + [ 'core', 'queries' ], + [ 'vendor' ], + ] ); + } ); + it( `returns true if no parent or descendant has any locks`, () => { + expect( + __unstableIsLockAvailable( + deepFreeze( state ), + 'core', + [ 'entities', 'root' ], + { exclusive: true } + ) + ).toBe( true ); + } ); + + it( `returns true if another branch holds a locks (1)`, () => { + appendLock( state, 'core', [ 'queries' ], { + exclusive: true, + } ); + expect( + __unstableIsLockAvailable( + deepFreeze( state ), + 'core', + [ 'entities', 'root' ], + { exclusive: true } + ) + ).toBe( true ); + } ); + + it( `returns true if another branch holds a locks (2)`, () => { + appendLock( state, 'vendor', [], { + exclusive: true, + } ); + expect( + __unstableIsLockAvailable( + deepFreeze( state ), + 'core', + [ 'entities', 'root' ], + { exclusive: true } + ) + ).toBe( true ); + } ); + + it( `returns true if another branch holds a locks (3)`, () => { + const subState = { + locks: { + tree: { + locks: [], + children: { + postType: { + locks: [], + children: { + post: { + locks: [], + children: { + 16: { + locks: [ + { + store: 'core', + path: [ + 'entities', + 'data', + 'postType', + 'post', + 16, + ], + exclusive: true, + }, + ], + children: {}, + }, + }, + }, + wp_template_part: { + locks: [], + children: { + 17: { + locks: [], + children: {}, + }, + }, + }, + }, + }, + }, + }, + }, + }; + expect( + __unstableIsLockAvailable( + deepFreeze( subState ), + 'core', + [ 'postType', 'wp_template_part', 17 ], + { exclusive: true } + ) + ).toBe( true ); + } ); + + it( `returns true if another branch holds a locks (4)`, () => { + const subState = { + locks: { + tree: { + locks: [], + children: { + core: { + locks: [], + children: { + entities: { + locks: [], + children: { + data: { + locks: [], + children: { + postType: { + locks: [], + children: { + book: { + locks: [], + children: { + 67: { + locks: [ + { + path: [ + 'core', + 'entities', + 'data', + 'postType', + 'book', + 67, + ], + exclusive: true, + }, + ], + children: {}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }; + expect( + __unstableIsLockAvailable( + deepFreeze( subState ), + 'core', + [ 'entities', 'data', 'postType', 'book', 67 ], + { exclusive: false } + ) + ).toBe( false ); + } ); + + [ true, false ].forEach( ( exclusive ) => { + it( `returns true if the path is not accessible and no parent holds a lock`, () => { + expect( + __unstableIsLockAvailable( + deepFreeze( state ), + 'core', + [ 'fake', 'path' ], + { exclusive: true } + ) + ).toBe( true ); + } ); + + it( `returns false if the path is not accessible and any parent holds a lock`, () => { + appendLock( state, 'core', [], { + exclusive, + } ); + expect( + __unstableIsLockAvailable( + deepFreeze( state ), + 'core', + [ 'fake', 'path' ], + { exclusive: true } + ) + ).toBe( false ); + } ); + + it( `returns false if top-level parent already has a lock with exclusive=${ exclusive }`, () => { + appendLock( state, 'core', [], { + exclusive, + } ); + expect( + __unstableIsLockAvailable( + deepFreeze( state ), + 'core', + [ 'entities', 'root' ], + { exclusive: true } + ) + ).toBe( false ); + } ); + + it( `returns false if a direct parent already has a lock with exclusive=${ exclusive }`, () => { + appendLock( state, 'core', [ 'entities' ], { + exclusive, + } ); + expect( + __unstableIsLockAvailable( + deepFreeze( state ), + 'core', + [ 'entities', 'root' ], + { exclusive: true } + ) + ).toBe( false ); + } ); + + it( `returns false if the target node already has a lock with exclusive=${ exclusive }`, () => { + appendLock( state, 'core', [ 'entities', 'root' ], { + exclusive, + } ); + expect( + __unstableIsLockAvailable( + deepFreeze( state ), + 'core', + [ 'entities', 'root' ], + { exclusive: true } + ) + ).toBe( false ); + } ); + + it( `returns false if a children node already has a lock with exclusive=${ exclusive }`, () => { + appendLock( + state, + 'core', + [ 'entities', 'root', 'template_part' ], + { + exclusive, + } + ); + expect( + __unstableIsLockAvailable( + deepFreeze( state ), + 'core', + [ 'entities', 'root' ], + { exclusive: true } + ) + ).toBe( false ); + } ); + + it( `returns false if a grand-children node already has a lock with exclusive=${ exclusive }`, () => { + appendLock( + state, + 'core', + [ 'entities', 'root', 'template_part', '3' ], + { + exclusive, + } + ); + expect( + __unstableIsLockAvailable( + deepFreeze( state ), + 'core', + [ 'entities', 'root' ], + { exclusive: true } + ) + ).toBe( false ); + } ); + } ); + } ); + + describe( 'Advanced cases - shared lock', () => { + let state; + beforeEach( () => { + state = buildState( [ + [ 'core', 'entities', 'root', 'template_part', '3' ], + ] ); + } ); + it( `returns true if no parent or descendant has any locks`, () => { + expect( + __unstableIsLockAvailable( + deepFreeze( state ), + 'core', + [ 'entities', 'root' ], + { exclusive: true } + ) + ).toBe( true ); + } ); + + [ true, false ].forEach( ( isOtherLockExclusive ) => { + const expectation = ! isOtherLockExclusive; + it( `returns ${ expectation } if the path is not accessible and any parent holds a lock exclusive=${ isOtherLockExclusive }`, () => { + appendLock( state, 'core', [], { + exclusive: isOtherLockExclusive, + } ); + expect( + __unstableIsLockAvailable( + deepFreeze( state ), + 'core', + [ 'fake', 'path' ], + { exclusive: false } + ) + ).toBe( expectation ); + } ); + + it( `returns ${ expectation } if top-level parent already has a lock with exclusive=${ isOtherLockExclusive }`, () => { + appendLock( state, 'core', [], { + exclusive: isOtherLockExclusive, + } ); + expect( + __unstableIsLockAvailable( + deepFreeze( state ), + 'core', + [ 'entities', 'root' ], + { exclusive: false } + ) + ).toBe( expectation ); + } ); + + it( `returns ${ expectation } if a direct parent already has a lock with exclusive=${ isOtherLockExclusive }`, () => { + appendLock( state, 'core', [ 'entities' ], { + exclusive: isOtherLockExclusive, + } ); + expect( + __unstableIsLockAvailable( + deepFreeze( state ), + 'core', + [ 'entities', 'root' ], + { exclusive: false } + ) + ).toBe( expectation ); + } ); + + it( `returns ${ expectation } if the target node already has a lock with exclusive=${ isOtherLockExclusive }`, () => { + appendLock( state, 'core', [ 'entities', 'root' ], { + exclusive: isOtherLockExclusive, + } ); + expect( + __unstableIsLockAvailable( + deepFreeze( state ), + 'core', + [ 'entities', 'root' ], + { exclusive: false } + ) + ).toBe( expectation ); + } ); + + it( `returns ${ expectation } if a children node already has a lock with exclusive=${ isOtherLockExclusive }`, () => { + appendLock( + state, + 'core', + [ 'entities', 'root', 'template_part' ], + { + exclusive: isOtherLockExclusive, + } + ); + expect( + __unstableIsLockAvailable( + deepFreeze( state ), + 'core', + [ 'entities', 'root' ], + { exclusive: false } + ) + ).toBe( expectation ); + } ); + + it( `returns ${ expectation } if a grand-children node already has a lock with exclusive=${ isOtherLockExclusive }`, () => { + appendLock( + state, + 'core', + [ 'entities', 'root', 'template_part', '3' ], + { + exclusive: isOtherLockExclusive, + } + ); + expect( + __unstableIsLockAvailable( + deepFreeze( state ), + 'core', + [ 'entities', 'root' ], + { exclusive: false } + ) + ).toBe( expectation ); + } ); + } ); + } ); +} ); + +function appendLock( state, store, path, lock ) { + getNode( state.locks.tree, [ store, ...path ] ).locks.push( lock ); +} + +function buildState( paths ) { + return { + locks: { + requests: [], + tree: paths.reduce( + ( tree, path ) => deepCopyLocksTreePath( tree, path ), + { + locks: [], + children: {}, + } + ), + }, + }; +} diff --git a/packages/core-data/src/locks/test/utils.js b/packages/core-data/src/locks/test/utils.js new file mode 100644 index 00000000000000..f712e1a2b001f0 --- /dev/null +++ b/packages/core-data/src/locks/test/utils.js @@ -0,0 +1,243 @@ +/** + * External dependencies + */ +import deepFreeze from 'deep-freeze'; + +/** + * Internal dependencies + */ +import { + getNode, + iteratePath, + iterateDescendants, + hasConflictingLock, + deepCopyLocksTreePath, +} from '../utils'; + +describe( 'getNode', () => { + const tree = deepFreeze( { + locks: [], + children: { + target: { + locks: [], + children: {}, + }, + }, + } ); + + it( 'Returns a specified node from the tree', () => { + expect( getNode( tree, [] ) ).toBe( tree ); + expect( getNode( tree, [ 'target' ] ) ).toBe( tree.children.target ); + } ); + it( 'Returns null when specified node does not exist', () => { + expect( getNode( tree, [ 'fake' ] ) ).toBe( null ); + } ); +} ); + +describe( 'iteratePath', () => { + const buildNode = ( children = {} ) => ( { + locks: [], + children, + } ); + const red = buildNode(); + const blue = buildNode(); + const green = buildNode(); + const bird = buildNode( { red, blue, green } ); + const target = buildNode( { bird } ); + const tree = deepFreeze( buildNode( { target } ) ); + + it( 'Is a generator', () => { + const isGenerator = [ + 'GeneratorFunction', + 'AsyncGeneratorFunction', + ].includes( iteratePath.constructor.name ); + expect( isGenerator ).toBe( true ); + } ); + + it( 'Yields the root first', () => { + expect( + Array.from( iteratePath( tree, [ 'target', 'bird' ] ) )[ 0 ] + ).toEqual( tree ); + } ); + + it( 'Iterates over a specified tree path', () => { + expect( + Array.from( iteratePath( tree, [ 'target', 'bird', 'green' ] ) ) + ).toEqual( [ tree, target, bird, green ] ); + } ); + + it( 'Yields just the root if the path is empty', () => { + expect( Array.from( iteratePath( tree, [] ) ) ).toEqual( [ tree ] ); + } ); + + it( 'Yields only the accessible nodes in the path', () => { + expect( + Array.from( iteratePath( tree, [ 'target', 'bird', 'fake' ] ) ) + ).toEqual( [ tree, target, bird ] ); + } ); +} ); + +describe( 'iterateDescendants', () => { + const buildNode = ( children = {} ) => ( { + locks: [], + children, + } ); + const red = buildNode(); + const blue = buildNode(); + const green = buildNode(); + const bird = buildNode( { red, blue, green } ); + const big = buildNode(); + const small = buildNode(); + const large = buildNode(); + const house = buildNode( { big, small, large } ); + + const tree = buildNode( { bird, house } ); + + it( 'Is a generator', () => { + const isGenerator = [ + 'GeneratorFunction', + 'AsyncGeneratorFunction', + ].includes( iterateDescendants.constructor.name ); + expect( isGenerator ).toBe( true ); + } ); + + it( 'Yields nothing when no children are available', () => { + expect( Array.from( iterateDescendants( red ) ) ).toEqual( [] ); + } ); + + it( 'Yields children when there is just one level of nesting', () => { + const results = Array.from( iterateDescendants( bird ) ); + expect( results ).toHaveLength( 3 ); + expect( results ).toEqual( + expect.arrayContaining( [ red, blue, green ] ) + ); + } ); + + it( 'Yields all descendants when ran on deep structure', () => { + const results = Array.from( iterateDescendants( tree ) ); + expect( results ).toHaveLength( 8 ); + expect( results ).toEqual( + expect.arrayContaining( [ + bird, + red, + blue, + green, + house, + big, + small, + green, + ] ) + ); + } ); +} ); + +describe( 'hasConflictingLock', () => { + it( 'Returns false when requesting an exclusive lock and no locks are present', () => { + expect( hasConflictingLock( { exclusive: true }, [] ) ).toBe( false ); + } ); + it( 'Returns true when requesting an exclusive lock and any locks at all are present', () => { + expect( + hasConflictingLock( { exclusive: true }, [ { exclusive: false } ] ) + ).toBe( true ); + expect( + hasConflictingLock( { exclusive: true }, [ { exclusive: true } ] ) + ).toBe( true ); + } ); + + it( 'Returns false when requesting a shared lock and no locks are present', () => { + expect( hasConflictingLock( { exclusive: false }, [] ) ).toBe( false ); + } ); + + it( 'Returns false when requesting a shared lock and only shared locks at all are present', () => { + expect( + hasConflictingLock( { exclusive: false }, [ { exclusive: false } ] ) + ).toBe( false ); + expect( + hasConflictingLock( { exclusive: false }, [ + { exclusive: false }, + { exclusive: false }, + { exclusive: false }, + ] ) + ).toBe( false ); + } ); + + it( 'Returns true when requesting a shared lock and any exclusive shared locks at all are present', () => { + expect( + hasConflictingLock( { exclusive: false }, [ { exclusive: true } ] ) + ).toBe( true ); + expect( + hasConflictingLock( { exclusive: false }, [ + { exclusive: false }, + { exclusive: true }, + { exclusive: false }, + ] ) + ).toBe( true ); + } ); +} ); + +describe( 'deepCopyLocksTreePath', () => { + it( 'Returns a tree with a cloned path', () => { + const tree = deepFreeze( { + locks: [ { exclusive: true } ], + children: { + target: { + locks: [], + children: {}, + }, + }, + } ); + + const deepCopy = deepCopyLocksTreePath( tree, [ 'target' ] ); + expect( deepCopy ).toMatchObject( tree ); + // Path to the target node should be cloned (named branches and their `children` containers) + expect( deepCopy.children ).not.toBe( tree.children ); + expect( deepCopy.children.target ).not.toBe( tree.children.target ); + + // Locks lists should be preserved + expect( deepCopy.locks ).toBe( tree.locks ); + expect( deepCopy.children.target.locks ).toBe( + tree.children.target.locks + ); + + // Specific locks should be preserved + expect( deepCopy.locks[ 0 ] ).toBe( tree.locks[ 0 ] ); + + // No need to clone lower levels of the tree, let's check if they're still the same: + expect( deepCopy.children.target.children ).toBe( + tree.children.target.children + ); + } ); + + it( 'Creates missing branches', () => { + const tree = deepFreeze( { + locks: [ { exclusive: true } ], + children: { + target: { + locks: [], + children: {}, + }, + }, + } ); + + const deepCopy = deepCopyLocksTreePath( + tree, + [ 'target', 'bird', 'blue' ], + { + createMissing: true, + } + ); + const target = deepCopy.children.target; + expect( Object.keys( target.children ) ).toEqual( [ 'bird' ] ); + expect( Object.keys( target.children.bird ) ).toEqual( [ + 'locks', + 'children', + ] ); + expect( Object.keys( target.children.bird.children ) ).toEqual( [ + 'blue', + ] ); + expect( Object.keys( target.children.bird.children.blue ) ).toEqual( [ + 'locks', + 'children', + ] ); + } ); +} ); diff --git a/packages/core-data/src/locks/utils.js b/packages/core-data/src/locks/utils.js new file mode 100644 index 00000000000000..13735b49a36ec9 --- /dev/null +++ b/packages/core-data/src/locks/utils.js @@ -0,0 +1,62 @@ +export function deepCopyLocksTreePath( tree, path ) { + const newTree = { ...tree }; + let currentNode = newTree; + for ( const branchName of path ) { + currentNode.children = { + ...currentNode.children, + [ branchName ]: { + locks: [], + children: {}, + ...currentNode.children[ branchName ], + }, + }; + currentNode = currentNode.children[ branchName ]; + } + return newTree; +} + +export function getNode( tree, path ) { + let currentNode = tree; + for ( const branchName of path ) { + const nextNode = currentNode.children[ branchName ]; + if ( ! nextNode ) { + return null; + } + currentNode = nextNode; + } + return currentNode; +} + +export function* iteratePath( tree, path ) { + let currentNode = tree; + yield currentNode; + for ( const branchName of path ) { + const nextNode = currentNode.children[ branchName ]; + if ( ! nextNode ) { + break; + } + yield nextNode; + currentNode = nextNode; + } +} + +export function* iterateDescendants( node ) { + const stack = Object.values( node.children ); + while ( stack.length ) { + const childNode = stack.pop(); + yield childNode; + stack.push( ...Object.values( childNode.children ) ); + } +} + +export function hasConflictingLock( { exclusive }, locks ) { + if ( exclusive && locks.length ) { + return true; + } + + if ( ! exclusive && locks.filter( ( lock ) => lock.exclusive ).length ) { + return true; + } + + return false; +} diff --git a/packages/core-data/src/name.js b/packages/core-data/src/name.js index 6049841dc94a53..eb5122fbee117f 100644 --- a/packages/core-data/src/name.js +++ b/packages/core-data/src/name.js @@ -4,4 +4,4 @@ * * @type {string} */ -export const REDUCER_KEY = 'core'; +export const STORE_NAME = 'core'; diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index 8238c42380da9f..5bbe9ccbb855a2 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -15,6 +15,7 @@ import isShallowEqual from '@wordpress/is-shallow-equal'; import { ifMatchingAction, replaceAction } from './utils'; import { reducer as queriedDataReducer } from './queried-data'; import { defaultEntities, DEFAULT_ENTITY_KEY } from './entities'; +import { reducer as locksReducer } from './locks'; /** * Reducer managing terms state. Keyed by taxonomy slug, the value is either @@ -567,4 +568,5 @@ export default combineReducers( { embedPreviews, userPermissions, autosaves, + locks: locksReducer, } ); diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 69e452142554e8..3458c260d3f603 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -8,7 +8,12 @@ import { find, includes, get, hasIn, compact, uniq } from 'lodash'; */ import { addQueryArgs } from '@wordpress/url'; import deprecated from '@wordpress/deprecated'; -import { apiFetch, select, syncSelect } from '@wordpress/data-controls'; +import { controls } from '@wordpress/data'; +import { apiFetch } from '@wordpress/data-controls'; +/** + * Internal dependencies + */ +import { regularFetch } from './controls'; /** * Internal dependencies @@ -25,15 +30,35 @@ import { } from './actions'; import { getKindEntities, DEFAULT_ENTITY_KEY } from './entities'; import { ifNotResolved, getNormalizedCommaSeparable } from './utils'; +import { + __unstableAcquireStoreLock, + __unstableReleaseStoreLock, +} from './locks'; /** * Requests authors from the REST API. + * + * @param {Object|undefined} query Optional object of query parameters to + * include with request. */ -export function* getAuthors() { - const users = yield apiFetch( { - path: '/wp/v2/users/?who=authors&per_page=-1', - } ); - yield receiveUserQuery( 'authors', users ); +export function* getAuthors( query ) { + const path = addQueryArgs( + '/wp/v2/users/?who=authors&per_page=100', + query + ); + const users = yield apiFetch( { path } ); + yield receiveUserQuery( path, users ); +} + +/** + * Temporary approach to resolving editor access to author queries. + * + * @param {number} id The author id. + */ +export function* __unstableGetAuthor( id ) { + const path = `/wp/v2/users?who=authors&include=${ id }`; + const users = yield apiFetch( { path } ); + yield receiveUserQuery( 'author', users ); } /** @@ -60,51 +85,60 @@ export function* getEntityRecord( kind, name, key = '', query ) { return; } - if ( query !== undefined && query._fields ) { - // If requesting specific fields, items and query assocation to said - // records are stored by ID reference. Thus, fields must always include - // the ID. - query = { - ...query, - _fields: uniq( [ - ...( getNormalizedCommaSeparable( query._fields ) || [] ), - entity.key || DEFAULT_ENTITY_KEY, - ] ).join(), - }; - } + const lock = yield* __unstableAcquireStoreLock( + 'core', + [ 'entities', 'data', kind, name, key ], + { exclusive: false } + ); + try { + if ( query !== undefined && query._fields ) { + // If requesting specific fields, items and query assocation to said + // records are stored by ID reference. Thus, fields must always include + // the ID. + query = { + ...query, + _fields: uniq( [ + ...( getNormalizedCommaSeparable( query._fields ) || [] ), + entity.key || DEFAULT_ENTITY_KEY, + ] ).join(), + }; + } - // Disable reason: While true that an early return could leave `path` - // unused, it's important that path is derived using the query prior to - // additional query modifications in the condition below, since those - // modifications are relevant to how the data is tracked in state, and not - // for how the request is made to the REST API. + // Disable reason: While true that an early return could leave `path` + // unused, it's important that path is derived using the query prior to + // additional query modifications in the condition below, since those + // modifications are relevant to how the data is tracked in state, and not + // for how the request is made to the REST API. - // eslint-disable-next-line @wordpress/no-unused-vars-before-return - const path = addQueryArgs( entity.baseURL + '/' + key, { - ...query, - context: 'edit', - } ); + // eslint-disable-next-line @wordpress/no-unused-vars-before-return + const path = addQueryArgs( entity.baseURL + '/' + key, { + ...query, + context: 'edit', + } ); - if ( query !== undefined ) { - query = { ...query, include: [ key ] }; - - // The resolution cache won't consider query as reusable based on the - // fields, so it's tested here, prior to initiating the REST request, - // and without causing `getEntityRecords` resolution to occur. - const hasRecords = yield syncSelect( - 'core', - 'hasEntityRecords', - kind, - name, - query - ); - if ( hasRecords ) { - return; + if ( query !== undefined ) { + query = { ...query, include: [ key ] }; + + // The resolution cache won't consider query as reusable based on the + // fields, so it's tested here, prior to initiating the REST request, + // and without causing `getEntityRecords` resolution to occur. + const hasRecords = yield controls.select( + 'core', + 'hasEntityRecords', + kind, + name, + query + ); + if ( hasRecords ) { + return; + } } - } - const record = yield apiFetch( { path } ); - yield receiveEntityRecords( kind, name, record, query ); + const record = yield apiFetch( { path } ); + yield receiveEntityRecords( kind, name, record, query ); + } finally { + yield* __unstableReleaseStoreLock( lock ); + } } /** @@ -137,41 +171,70 @@ export function* getEntityRecords( kind, name, query = {} ) { return; } - if ( query._fields ) { - // If requesting specific fields, items and query assocation to said - // records are stored by ID reference. Thus, fields must always include - // the ID. - query = { - ...query, - _fields: uniq( [ - ...( getNormalizedCommaSeparable( query._fields ) || [] ), - entity.key || DEFAULT_ENTITY_KEY, - ] ).join(), - }; - } + const lock = yield* __unstableAcquireStoreLock( + 'core', + [ 'entities', 'data', kind, name ], + { exclusive: false } + ); + try { + if ( query._fields ) { + // If requesting specific fields, items and query assocation to said + // records are stored by ID reference. Thus, fields must always include + // the ID. + query = { + ...query, + _fields: uniq( [ + ...( getNormalizedCommaSeparable( query._fields ) || [] ), + entity.key || DEFAULT_ENTITY_KEY, + ] ).join(), + }; + } - const path = addQueryArgs( entity.baseURL, { - ...query, - context: 'edit', - } ); + const path = addQueryArgs( entity.baseURL, { + ...query, + context: 'edit', + } ); - let records = Object.values( yield apiFetch( { path } ) ); - // If we request fields but the result doesn't contain the fields, - // explicitely set these fields as "undefined" - // that way we consider the query "fullfilled". - if ( query._fields ) { - records = records.map( ( record ) => { - query._fields.split( ',' ).forEach( ( field ) => { - if ( ! record.hasOwnProperty( field ) ) { - record[ field ] = undefined; - } + let records = Object.values( yield apiFetch( { path } ) ); + // If we request fields but the result doesn't contain the fields, + // explicitely set these fields as "undefined" + // that way we consider the query "fullfilled". + if ( query._fields ) { + records = records.map( ( record ) => { + query._fields.split( ',' ).forEach( ( field ) => { + if ( ! record.hasOwnProperty( field ) ) { + record[ field ] = undefined; + } + } ); + + return record; } ); + } - return record; - } ); + yield receiveEntityRecords( kind, name, records, query ); + // When requesting all fields, the list of results can be used to + // resolve the `getEntityRecord` selector in addition to `getEntityRecords`. + // See https://github.com/WordPress/gutenberg/pull/26575 + if ( ! query?._fields ) { + const key = entity.key || DEFAULT_ENTITY_KEY; + for ( const record of records ) { + if ( record[ key ] ) { + yield { + type: 'START_RESOLUTION', + selectorName: 'getEntityRecord', + args: [ kind, name, record[ key ] ], + }; + yield { + type: 'FINISH_RESOLUTION', + selectorName: 'getEntityRecord', + args: [ kind, name, record[ key ] ], + }; + } + } + } + } finally { + yield* __unstableReleaseStoreLock( lock ); } - - yield receiveEntityRecords( kind, name, records, query ); } getEntityRecords.shouldInvalidate = ( action, kind, name ) => { @@ -297,7 +360,7 @@ export function* canUser( action, resource, id ) { * @param {number} postId The id of the parent post. */ export function* getAutosaves( postType, postId ) { - const { rest_base: restBase } = yield select( + const { rest_base: restBase } = yield controls.resolveSelect( 'core', 'getPostType', postType @@ -321,5 +384,40 @@ export function* getAutosaves( postType, postId ) { * @param {number} postId The id of the parent post. */ export function* getAutosave( postType, postId ) { - yield select( 'core', 'getAutosaves', postType, postId ); + yield controls.resolveSelect( 'core', 'getAutosaves', postType, postId ); +} + +/** + * Retrieve the frontend template used for a given link. + * + * @param {string} link Link. + */ +export function* __experimentalGetTemplateForLink( link ) { + // Ideally this should be using an apiFetch call + // We could potentially do so by adding a "filter" to the `wp_template` end point. + // Also it seems the returned object is not a regular REST API post type. + const template = yield regularFetch( + addQueryArgs( link, { + '_wp-find-template': true, + } ) + ); + + if ( template === null ) { + return; + } + + yield getEntityRecord( 'postType', 'wp_template', template.ID ); + const record = yield controls.select( + 'core', + 'getEntityRecord', + 'postType', + 'wp_template', + template.ID + ); + + if ( record ) { + yield receiveEntityRecords( 'postType', 'wp_template', [ record ], { + 'find-template': link, + } ); + } } diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js index 429fbab424fec2..5f4a9501f9bd54 100644 --- a/packages/core-data/src/selectors.js +++ b/packages/core-data/src/selectors.js @@ -9,15 +9,27 @@ import { set, map, find, get, filter, compact, defaultTo } from 'lodash'; */ import { createRegistrySelector } from '@wordpress/data'; import deprecated from '@wordpress/deprecated'; +import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ -import { REDUCER_KEY } from './name'; +import { STORE_NAME } from './name'; import { getQueriedItems } from './queried-data'; import { DEFAULT_ENTITY_KEY } from './entities'; import { getNormalizedCommaSeparable } from './utils'; +/** + * Shared reference to an empty array for cases where it is important to avoid + * returning a new array reference on every invocation, as in a connected or + * other pure component which performs `shouldComponentUpdate` check on props. + * This should be used as a last resort, since the normalized data should be + * maintained by the reducer result in state. + * + * @type {Array} + */ +const EMPTY_ARRAY = []; + /** * Returns true if a request is in progress for embed preview data, or false * otherwise. @@ -30,25 +42,39 @@ import { getNormalizedCommaSeparable } from './utils'; export const isRequestingEmbedPreview = createRegistrySelector( ( select ) => ( state, url ) => { return select( 'core/data' ).isResolving( - REDUCER_KEY, + STORE_NAME, 'getEmbedPreview', [ url ] ); } ); +/** + * Returns all available authors. + * + * @param {Object} state Data state. + * @param {Object|undefined} query Optional object of query parameters to + * include with request. + * @return {Array} Authors list. + */ +export function getAuthors( state, query ) { + const path = addQueryArgs( + '/wp/v2/users/?who=authors&per_page=100', + query + ); + return getUserQueryResults( state, path ); +} + /** * Returns all available authors. * * @param {Object} state Data state. + * @param {number} id The author id. * * @return {Array} Authors list. */ -export function getAuthors( state ) { - deprecated( "select( 'core' ).getAuthors()", { - alternative: "select( 'core' ).getUsers({ who: 'authors' })", - } ); - return getUserQueryResults( state, 'authors' ); +export function __unstableGetAuthor( state, id ) { + return get( state, [ 'users', 'byId', id ], null ); } /** @@ -238,7 +264,7 @@ export function getEntityRecords( state, kind, name, query ) { 'queriedData', ] ); if ( ! queriedState ) { - return []; + return EMPTY_ARRAY; } return getQueriedItems( queriedState, query ); } @@ -280,9 +306,7 @@ export const __experimentalGetDirtyEntityRecords = createSelector( entityRecord[ entity.key || DEFAULT_ENTITY_KEY ], - title: ! entity.getTitle - ? '' - : entity.getTitle( entityRecord ), + title: entity?.getTitle?.( entityRecord ) || '', name, kind, } ); @@ -683,7 +707,7 @@ export function getAutosave( state, postType, postId, authorId ) { */ export const hasFetchedAutosaves = createRegistrySelector( ( select ) => ( state, postType, postId ) => { - return select( REDUCER_KEY ).hasFinishedResolution( 'getAutosaves', [ + return select( STORE_NAME ).hasFinishedResolution( 'getAutosaves', [ postType, postId, ] ); @@ -716,3 +740,19 @@ export const getReferenceByDistinctEdits = createSelector( state.undo.flattenedUndo, ] ); + +/** + * Retrieve the frontend template used for a given link. + * + * @param {Object} state Editor state. + * @param {string} link Link. + * + * @return {Object?} The template record. + */ +export function __experimentalGetTemplateForLink( state, link ) { + const records = getEntityRecords( state, 'postType', 'wp_template', { + 'find-template': link, + } ); + + return records?.length ? records[ 0 ] : null; +} diff --git a/packages/core-data/src/test/actions.js b/packages/core-data/src/test/actions.js index 2527c8bbe096ea..4dbad511f4503f 100644 --- a/packages/core-data/src/test/actions.js +++ b/packages/core-data/src/test/actions.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { syncSelect } from '@wordpress/data-controls'; +import { controls } from '@wordpress/data'; /** * Internal dependencies @@ -16,6 +16,19 @@ import { receiveCurrentUser, } from '../actions'; +jest.mock( '../locks/actions', () => ( { + __unstableAcquireStoreLock: jest.fn( () => [ + { + type: 'MOCKED_ACQUIRE_LOCK', + }, + ] ), + __unstableReleaseStoreLock: jest.fn( () => [ + { + type: 'MOCKED_RELEASE_LOCK', + }, + ] ), +} ) ); + describe( 'editEntityRecord', () => { it( 'throws when the edited entity does not have a loaded config.', () => { const entity = { kind: 'someKind', name: 'someName', id: 'someId' }; @@ -26,8 +39,9 @@ describe( 'editEntityRecord', () => { {} ); expect( fulfillment.next().value ).toEqual( - syncSelect( 'core', 'getEntity', entity.kind, entity.name ) + controls.select( 'core', 'getEntity', entity.kind, entity.name ) ); + // Don't pass back an entity config. expect( fulfillment.next.bind( fulfillment ) ).toThrow( `The entity being edited (${ entity.kind }, ${ entity.name }) does not have a loaded config.` @@ -46,8 +60,13 @@ describe( 'deleteEntityRecord', () => { // Trigger generator fulfillment.next(); - // Start + // Acquire lock expect( fulfillment.next( entities ).value.type ).toBe( + 'MOCKED_ACQUIRE_LOCK' + ); + + // Start + expect( fulfillment.next().value.type ).toEqual( 'DELETE_ENTITY_RECORD_START' ); @@ -64,6 +83,11 @@ describe( 'deleteEntityRecord', () => { 'DELETE_ENTITY_RECORD_FINISH' ); + // Release lock + expect( fulfillment.next().value.type ).toEqual( + 'MOCKED_RELEASE_LOCK' + ); + expect( fulfillment.next() ).toMatchObject( { done: true, value: undefined, @@ -80,8 +104,14 @@ describe( 'saveEntityRecord', () => { const fulfillment = saveEntityRecord( 'postType', 'post', post ); // Trigger generator fulfillment.next(); - // Provide entities and trigger apiFetch + + // Provide entities and acquire lock expect( fulfillment.next( entities ).value.type ).toBe( + 'MOCKED_ACQUIRE_LOCK' + ); + + // Trigger apiFetch + expect( fulfillment.next().value.type ).toEqual( 'SAVE_ENTITY_RECORD_START' ); @@ -92,7 +122,9 @@ describe( 'saveEntityRecord', () => { '__experimentalGetEntityRecordNoResolver' ); expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' ); - expect( fulfillment.next().value.type ).toBe( 'RECEIVE_ITEMS' ); + const receiveItems = fulfillment.next().value; + expect( receiveItems.type ).toBe( 'RECEIVE_ITEMS' ); + expect( receiveItems.invalidateCache ).toBe( false ); const { value: apiFetchAction } = fulfillment.next( {} ); expect( apiFetchAction.request ).toEqual( { path: '/wp/v2/posts', @@ -114,6 +146,11 @@ describe( 'saveEntityRecord', () => { expect( fulfillment.next().value.type ).toBe( 'SAVE_ENTITY_RECORD_FINISH' ); + // Release lock + expect( fulfillment.next().value.type ).toEqual( + 'MOCKED_RELEASE_LOCK' + ); + expect( fulfillment.next().value ).toBe( updatedRecord ); } ); @@ -125,14 +162,22 @@ describe( 'saveEntityRecord', () => { const fulfillment = saveEntityRecord( 'postType', 'post', post ); // Trigger generator fulfillment.next(); - // Provide entities and trigger apiFetch + + // Provide entities and acquire lock expect( fulfillment.next( entities ).value.type ).toBe( + 'MOCKED_ACQUIRE_LOCK' + ); + + // Trigger apiFetch + expect( fulfillment.next().value.type ).toEqual( 'SAVE_ENTITY_RECORD_START' ); expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' ); expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' ); expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' ); - expect( fulfillment.next().value.type ).toBe( 'RECEIVE_ITEMS' ); + const receiveItems = fulfillment.next().value; + expect( receiveItems.type ).toBe( 'RECEIVE_ITEMS' ); + expect( receiveItems.invalidateCache ).toBe( false ); const { value: apiFetchAction } = fulfillment.next( {} ); expect( apiFetchAction.request ).toEqual( { path: '/wp/v2/posts/10', @@ -147,6 +192,10 @@ describe( 'saveEntityRecord', () => { expect( fulfillment.next().value.type ).toBe( 'SAVE_ENTITY_RECORD_FINISH' ); + // Release lock + expect( fulfillment.next().value.type ).toEqual( + 'MOCKED_RELEASE_LOCK' + ); } ); it( 'triggers a PUT request for an existing record with a custom key', async () => { @@ -162,8 +211,14 @@ describe( 'saveEntityRecord', () => { const fulfillment = saveEntityRecord( 'root', 'postType', postType ); // Trigger generator fulfillment.next(); - // Provide entities and trigger apiFetch + + // Provide entities and acquire lock expect( fulfillment.next( entities ).value.type ).toBe( + 'MOCKED_ACQUIRE_LOCK' + ); + + // Trigger apiFetch + expect( fulfillment.next().value.type ).toEqual( 'SAVE_ENTITY_RECORD_START' ); expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' ); @@ -190,6 +245,10 @@ describe( 'saveEntityRecord', () => { expect( fulfillment.next().value.type ).toBe( 'SAVE_ENTITY_RECORD_FINISH' ); + // Release lock + expect( fulfillment.next().value.type ).toEqual( + 'MOCKED_RELEASE_LOCK' + ); } ); } ); diff --git a/packages/core-data/src/test/entities.js b/packages/core-data/src/test/entities.js index e84e99ffb191f1..7d2bf1fecf161e 100644 --- a/packages/core-data/src/test/entities.js +++ b/packages/core-data/src/test/entities.js @@ -1,7 +1,13 @@ /** * Internal dependencies */ -import { getMethodName, defaultEntities, getKindEntities } from '../entities'; +import { + getMethodName, + defaultEntities, + getKindEntities, + getPostTypeTitle, + getPostTypePrePersistHandler, +} from '../entities'; import { addEntities } from '../actions'; describe( 'getMethodName', () => { @@ -79,3 +85,137 @@ describe( 'getKindEntities', () => { expect( end ).toEqual( { done: true, value: fetchedEntities } ); } ); } ); + +describe( 'getPostTypeTitle', () => { + it( 'should prefer the rendered value for titles for regular post types', async () => { + const record = { + id: 10, + title: { + rendered: 'My Title', + }, + }; + expect( getPostTypeTitle( 'post' )( record ) ).toBe( 'My Title' ); + } ); + + it( "should fallback to the title if it's a string", async () => { + const record = { + id: 10, + title: 'My Title', + }; + expect( getPostTypeTitle( 'post' )( record ) ).toBe( 'My Title' ); + } ); + + it( 'should fallback to the id if no title provided', async () => { + const record = { + id: 10, + }; + expect( getPostTypeTitle( 'post' )( record ) ).toBe( '10' ); + } ); + + it( 'should prefer the rendered value for titles for templates', async () => { + const record = { + slug: 'single', + title: { + rendered: 'My Template', + }, + }; + expect( getPostTypeTitle( 'wp_template' )( record ) ).toBe( + 'My Template' + ); + } ); + + it( "should fallback to the title if it's a string for templates", async () => { + const record = { + slug: 'single', + title: 'My Template', + }; + expect( getPostTypeTitle( 'wp_template' )( record ) ).toBe( + 'My Template' + ); + } ); + + it( 'should fallback to the slug if no title provided', async () => { + const record = { + slug: 'single', + }; + expect( getPostTypeTitle( 'wp_template' )( record ) ).toBe( 'Single' ); + } ); +} ); + +describe( 'getPostTypePrePersistHandler', () => { + it( 'set the status to draft and empty the title when saving auto-draft posts', () => { + let record = { + status: 'auto-draft', + }; + const edits = {}; + expect( + getPostTypePrePersistHandler( 'post' )( record, edits ) + ).toEqual( { + status: 'draft', + title: '', + } ); + + record = { + status: 'publish', + }; + expect( + getPostTypePrePersistHandler( 'post' )( record, edits ) + ).toEqual( {} ); + + record = { + status: 'auto-draft', + title: 'Auto Draft', + }; + expect( + getPostTypePrePersistHandler( 'post' )( record, edits ) + ).toEqual( { + status: 'draft', + title: '', + } ); + + record = { + status: 'publish', + title: 'My Title', + }; + expect( + getPostTypePrePersistHandler( 'post' )( record, edits ) + ).toEqual( {} ); + } ); + + it( 'should set the status of templates to publish and fix the title', () => { + let record = { + status: 'auto-draft', + slug: 'single', + }; + const edits = {}; + expect( + getPostTypePrePersistHandler( 'wp_template' )( record, edits ) + ).toEqual( { + status: 'publish', + title: 'Single', + } ); + + record = { + status: 'auto-draft', + }; + expect( + getPostTypePrePersistHandler( 'wp_template_part' )( record, edits ) + ).toEqual( { + status: 'publish', + title: '', + } ); + + record = { + status: 'auto-draft', + slug: 'single', + title: { + rendered: 'My title', + }, + }; + expect( + getPostTypePrePersistHandler( 'wp_template' )( record, edits ) + ).toEqual( { + status: 'publish', + } ); + } ); +} ); diff --git a/packages/core-data/src/test/integration.js b/packages/core-data/src/test/integration.js new file mode 100644 index 00000000000000..43fbdaeb27bd97 --- /dev/null +++ b/packages/core-data/src/test/integration.js @@ -0,0 +1,264 @@ +/** + * WordPress dependencies + */ +import { createRegistry, controls } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import * as actions from '../actions'; +import * as selectors from '../selectors'; +import * as resolvers from '../resolvers'; +import { store } from '../'; + +// Mock to prevent calling window.fetch in test environment +jest.mock( '@wordpress/data-controls', () => { + const dataControls = jest.requireActual( '@wordpress/data-controls' ); + return { + ...dataControls, + apiFetch: jest.fn(), + }; +} ); +const { apiFetch: actualApiFetch } = jest.requireActual( + '@wordpress/data-controls' +); +import { apiFetch } from '@wordpress/data-controls'; + +jest.mock( '@wordpress/api-fetch', () => { + return { + __esModule: true, + default: jest.fn(), + }; +} ); +import triggerFetch from '@wordpress/api-fetch'; + +const runPromise = async ( promise ) => { + jest.runAllTimers(); + await promise; +}; + +const runPendingPromises = async () => { + jest.runAllTimers(); + const p = new Promise( ( resolve ) => setTimeout( resolve ) ); + jest.runAllTimers(); + await p; +}; + +describe( 'receiveEntityRecord', () => { + function createTestRegistry( getEntityRecord ) { + const registry = createRegistry(); + const initialState = { + entities: { + data: {}, + }, + }; + registry.register( store ); + registry.registerStore( 'test/resolution', { + actions: { + receiveEntityRecords: actions.receiveEntityRecords, + *getEntityRecords( ...args ) { + return yield controls.resolveSelect( + 'test/resolution', + 'getEntityRecords', + ...args + ); + }, + *getEntityRecord( ...args ) { + return yield controls.resolveSelect( + 'test/resolution', + 'getEntityRecord', + ...args + ); + }, + }, + reducer: ( state = initialState ) => { + return state; + }, + selectors: { + getEntityRecord: selectors.getEntityRecord, + getEntityRecords: selectors.getEntityRecords, + }, + resolvers: { + getEntityRecord, + getEntityRecords: resolvers.getEntityRecords, + }, + } ); + return registry; + } + + beforeEach( async () => { + apiFetch.mockReset(); + triggerFetch.mockReset(); + jest.useFakeTimers(); + } ); + + it( 'should not trigger a resolver when the requested record is available via receiveEntityRecords (default entity key).', async () => { + const getEntityRecord = jest.fn(); + const registry = createTestRegistry( getEntityRecord ); + + // Trigger resolution of postType records + apiFetch.mockImplementation( () => ( { + 2: { slug: 'test', id: 2 }, + } ) ); + await runPromise( + registry + .dispatch( 'test/resolution' ) + .getEntityRecords( 'root', 'site' ) + ); + jest.runAllTimers(); + + // Select record with id = 2, it is available and should not trigger the resolver + await runPromise( + registry + .dispatch( 'test/resolution' ) + .getEntityRecord( 'root', 'site', 2 ) + ); + expect( getEntityRecord ).not.toHaveBeenCalled(); + + // Select record with id = 4, it is not available and should trigger the resolver + await runPromise( + registry + .dispatch( 'test/resolution' ) + .getEntityRecord( 'root', 'site', 4 ) + ); + expect( getEntityRecord ).toHaveBeenCalled(); + } ); + + it( 'should not trigger a resolver when the requested record is available via receiveEntityRecords (non-default entity key).', async () => { + const getEntityRecord = jest.fn(); + const registry = createTestRegistry( getEntityRecord ); + + // Trigger resolution of postType records + apiFetch.mockImplementation( () => ( { + 'test-1': { slug: 'test-1', id: 2 }, + } ) ); + await runPromise( + registry + .dispatch( 'test/resolution' ) + .getEntityRecords( 'root', 'taxonomy' ) + ); + jest.runAllTimers(); + + // Select record with id = test-1, it is available and should not trigger the resolver + await runPromise( + registry + .dispatch( 'test/resolution' ) + .getEntityRecord( 'root', 'taxonomy', 'test-1' ) + ); + expect( getEntityRecord ).not.toHaveBeenCalled(); + + // Select record with id = test-2, it is not available and should trigger the resolver + await runPromise( + registry + .dispatch( 'test/resolution' ) + .getEntityRecord( 'root', 'taxonomy', 'test-2' ) + ); + expect( getEntityRecord ).toHaveBeenCalled(); + } ); +} ); + +describe( 'saveEntityRecord', () => { + function createTestRegistry() { + const registry = createRegistry(); + registry.register( store ); + return registry; + } + + beforeEach( async () => { + apiFetch.mockReset(); + triggerFetch.mockReset(); + jest.useFakeTimers( 'modern' ); + } ); + + it( 'should not trigger any GET requests until POST/PUT is finished.', async () => { + const registry = createTestRegistry(); + // Fetch post types from the API {{{ + apiFetch.mockImplementation( () => ( { + 'post-1': { slug: 'post-1' }, + } ) ); + + // Trigger fetch + registry.select( 'core' ).getEntityRecords( 'root', 'postType' ); + jest.runAllTimers(); + await Promise.resolve().then( () => jest.runAllTimers() ); + expect( apiFetch ).toBeCalledTimes( 1 ); + expect( apiFetch ).toBeCalledWith( { + path: '/wp/v2/types?context=edit', + } ); + + // Select fetched results, there should be no subsequent request + apiFetch.mockReset(); + const results = registry + .select( 'core' ) + .getEntityRecords( 'root', 'postType' ); + expect( apiFetch ).toBeCalledTimes( 0 ); + jest.runAllTimers(); + expect( apiFetch ).toBeCalledTimes( 0 ); + expect( results ).toHaveLength( 1 ); + expect( results[ 0 ].slug ).toBe( 'post-1' ); + // }}} Fetch post types from the API + + // Save changes + apiFetch.mockClear(); + apiFetch.mockImplementation( actualApiFetch ); + let resolvePromise; + triggerFetch.mockImplementation( function () { + return new Promise( ( resolve ) => { + resolvePromise = resolve; + } ); + } ); + const savePromise = registry + .dispatch( 'core' ) + .saveEntityRecord( 'root', 'postType', { + slug: 'post-1', + newField: 'a', + } ); + await runPendingPromises(); + + // There should ONLY be a single hanging API call (PUT) by this point. + // If there have been any other requests, it is a race condition of some sorts, + // e.g. a resolution was triggered before the save was finished. + expect( triggerFetch ).toBeCalledTimes( 1 ); + expect( triggerFetch ).toHaveBeenCalledWith( + expect.objectContaining( { + method: 'PUT', + path: '/wp/v2/types/post-1', + data: expect.objectContaining( { + newField: 'a', + slug: 'post-1', + } ), + } ) + ); + triggerFetch.mockClear(); + apiFetch.mockClear(); + + // The PUT is still hanging, let's call a selector now and make sure it won't trigger + // any requests + registry.select( 'core' ).getEntityRecords( 'root', 'postType' ); + jest.runAllTimers(); + expect( triggerFetch ).toBeCalledTimes( 0 ); + + // Now that all timers are exhausted, let's resolve the PUT request and wait until the + // save is complete + resolvePromise( { newField: 'a', slug: 'post-1' } ); + + // Run selector and make sure it doesn't trigger any requests just yet + registry.select( 'core' ).getEntityRecords( 'root', 'postType' ); + jest.runAllTimers(); + expect( triggerFetch ).toBeCalledTimes( 0 ); + + const newRecord = await savePromise; + expect( newRecord ).toEqual( { newField: 'a', slug: 'post-1' } ); + // There should be no other API calls just because saving succeeded + jest.runAllTimers(); + expect( triggerFetch ).toBeCalledTimes( 0 ); + + // Calling the selector after the save is finished should trigger a resolver and a GET request + registry.select( 'core' ).getEntityRecords( 'root', 'postType' ); + await runPendingPromises(); + expect( triggerFetch ).toBeCalledTimes( 1 ); + expect( triggerFetch ).toBeCalledWith( { + path: '/wp/v2/types?context=edit', + } ); + } ); +} ); diff --git a/packages/core-data/src/test/resolvers.js b/packages/core-data/src/test/resolvers.js index f82ef7aee4c986..cf3a29bd072d79 100644 --- a/packages/core-data/src/test/resolvers.js +++ b/packages/core-data/src/test/resolvers.js @@ -22,6 +22,19 @@ import { receiveCurrentUser, } from '../actions'; +jest.mock( '../locks/actions', () => ( { + __unstableAcquireStoreLock: jest.fn( () => [ + { + type: 'MOCKED_ACQUIRE_LOCK', + }, + ] ), + __unstableReleaseStoreLock: jest.fn( () => [ + { + type: 'MOCKED_RELEASE_LOCK', + }, + ] ), +} ) ); + describe( 'getEntityRecord', () => { const POST_TYPE = { slug: 'post' }; @@ -32,8 +45,12 @@ describe( 'getEntityRecord', () => { const fulfillment = getEntityRecord( 'root', 'postType', 'post' ); // Trigger generator fulfillment.next(); - // Provide entities and trigger apiFetch - const { value: apiFetchAction } = fulfillment.next( entities ); + // Provide entities and acquire lock + expect( fulfillment.next( entities ).value.type ).toEqual( + 'MOCKED_ACQUIRE_LOCK' + ); + // trigger apiFetch + const { value: apiFetchAction } = fulfillment.next(); expect( apiFetchAction.request ).toEqual( { path: '/wp/v2/types/post?context=edit', } ); @@ -42,25 +59,35 @@ describe( 'getEntityRecord', () => { expect( received ).toEqual( receiveEntityRecords( 'root', 'postType', POST_TYPE ) ); + // Release lock + expect( fulfillment.next().value.type ).toEqual( + 'MOCKED_RELEASE_LOCK' + ); } ); } ); describe( 'getEntityRecords', () => { const POST_TYPES = { post: { slug: 'post' }, - page: { slug: 'page' }, + page: { slug: 'page', id: 2 }, }; + const ENTITIES = [ + { name: 'postType', kind: 'root', baseURL: '/wp/v2/types' }, + { name: 'postType', kind: 'root', baseURL: '/wp/v2/types' }, + ]; it( 'yields with requested post type', async () => { - const entities = [ - { name: 'postType', kind: 'root', baseURL: '/wp/v2/types' }, - ]; const fulfillment = getEntityRecords( 'root', 'postType' ); // Trigger generator fulfillment.next(); - // Provide entities and trigger apiFetch - const { value: apiFetchAction } = fulfillment.next( entities ); + + // Provide entities and acquire lock + fulfillment.next( ENTITIES ); + + // trigger apiFetch + const { value: apiFetchAction } = fulfillment.next(); + expect( apiFetchAction.request ).toEqual( { path: '/wp/v2/types?context=edit', } ); @@ -75,6 +102,50 @@ describe( 'getEntityRecords', () => { ) ); } ); + + it( 'Uses state locks', async () => { + const fulfillment = getEntityRecords( 'root', 'postType' ); + + // Repeat the steps from `yields with requested post type` test + fulfillment.next(); + // Provide entities and acquire lock + expect( fulfillment.next( ENTITIES ).value.type ).toEqual( + 'MOCKED_ACQUIRE_LOCK' + ); + fulfillment.next(); + fulfillment.next( POST_TYPES ); + + // Resolve specific entity records + fulfillment.next(); + fulfillment.next(); + + // Release lock + expect( fulfillment.next().value.type ).toEqual( + 'MOCKED_RELEASE_LOCK' + ); + } ); + + it( 'marks specific entity records as resolved', async () => { + const fulfillment = getEntityRecords( 'root', 'postType' ); + + // Repeat the steps from `yields with requested post type` test + fulfillment.next(); + fulfillment.next( ENTITIES ); + fulfillment.next(); + fulfillment.next( POST_TYPES ); + + // It should mark the entity record that has an ID as resolved + expect( fulfillment.next().value ).toEqual( { + type: 'START_RESOLUTION', + selectorName: 'getEntityRecord', + args: [ ENTITIES[ 1 ].kind, ENTITIES[ 1 ].name, 2 ], + } ); + expect( fulfillment.next().value ).toEqual( { + type: 'FINISH_RESOLUTION', + selectorName: 'getEntityRecord', + args: [ ENTITIES[ 1 ].kind, ENTITIES[ 1 ].name, 2 ], + } ); + } ); } ); describe( 'getEmbedPreview', () => { diff --git a/packages/core-data/src/test/selectors.js b/packages/core-data/src/test/selectors.js index 0da0e3c30df8e0..0ec684a21d33d6 100644 --- a/packages/core-data/src/test/selectors.js +++ b/packages/core-data/src/test/selectors.js @@ -247,6 +247,42 @@ describe( 'getEntityRecords', () => { { slug: 'page' }, ] ); } ); + + it( 'should return the same instance with the same arguments', () => { + let state = deepFreeze( { + entities: { + data: {}, + }, + } ); + + const postTypeFirstRecords = getEntityRecords( + state, + 'root', + 'postType' + ); + const wpBlockFirstRecords = getEntityRecords( + state, + 'postType', + 'wp_block' + ); + + // Simulate update states + state = { ...state }; + + const postTypeSecondRecords = getEntityRecords( + state, + 'root', + 'postType' + ); + const wpBlockSecondRecords = getEntityRecords( + state, + 'postType', + 'wp_block' + ); + + expect( postTypeFirstRecords ).toBe( postTypeSecondRecords ); + expect( wpBlockFirstRecords ).toBe( wpBlockSecondRecords ); + } ); } ); describe( '__experimentalGetDirtyEntityRecords', () => { diff --git a/packages/core-data/src/utils/if-not-resolved.js b/packages/core-data/src/utils/if-not-resolved.js index 2b3c25427f63f8..5b53350541be0d 100644 --- a/packages/core-data/src/utils/if-not-resolved.js +++ b/packages/core-data/src/utils/if-not-resolved.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { select } from '@wordpress/data-controls'; +import { controls } from '@wordpress/data'; /** * Higher-order function which invokes the given resolver only if it has not @@ -20,7 +20,7 @@ const ifNotResolved = ( resolver, selectorName ) => * @param {...any} args Original resolver arguments. */ function* resolveIfNotResolved( ...args ) { - const hasStartedResolution = yield select( + const hasStartedResolution = yield controls.select( 'core', 'hasStartedResolution', selectorName, diff --git a/packages/core-data/src/utils/test/if-not-resolved.js b/packages/core-data/src/utils/test/if-not-resolved.js index 7115d1f689d003..a773097d70514f 100644 --- a/packages/core-data/src/utils/test/if-not-resolved.js +++ b/packages/core-data/src/utils/test/if-not-resolved.js @@ -1,20 +1,22 @@ /** * WordPress dependencies */ -import { select } from '@wordpress/data-controls'; +import { controls } from '@wordpress/data'; /** * Internal dependencies */ import ifNotResolved from '../if-not-resolved'; -jest.mock( '@wordpress/data-controls', () => ( { - select: jest.fn(), +jest.mock( '@wordpress/data', () => ( { + controls: { + select: jest.fn(), + }, } ) ); describe( 'ifNotResolved', () => { beforeEach( () => { - select.mockReset(); + controls.select.mockReset(); } ); it( 'returns a new function', () => { @@ -26,7 +28,7 @@ describe( 'ifNotResolved', () => { } ); it( 'triggers original resolver if not already resolved', () => { - select.mockImplementation( ( _storeKey, selectorName ) => ( { + controls.select.mockImplementation( ( _storeKey, selectorName ) => ( { _nextValue: selectorName === 'hasStartedResolution' ? false : undefined, } ) ); @@ -49,7 +51,7 @@ describe( 'ifNotResolved', () => { } ); it( 'does not trigger original resolver if already resolved', () => { - select.mockImplementation( ( _storeKey, selectorName ) => ( { + controls.select.mockImplementation( ( _storeKey, selectorName ) => ( { _nextValue: selectorName === 'hasStartedResolution' ? true : undefined, } ) ); diff --git a/packages/create-block/CHANGELOG.md b/packages/create-block/CHANGELOG.md index 541bc0fe313084..73f8a1f1fc2bdb 100644 --- a/packages/create-block/CHANGELOG.md +++ b/packages/create-block/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased +### Breaking Changes + +- Set the minimum required version of WordPress to 5.6.0 to ensure that block is correctly registered with the [Block API version 2](https://make.wordpress.org/core/2020/11/18/block-api-version-2/) ([#26098](https://github.com/WordPress/gutenberg/pull/26098)). + +### New Features + +- Added basic support for external templates hosted on npm ([#23712](https://github.com/WordPress/gutenberg/pull/23712)). +- Update templates to work with the [Block API version 2](https://make.wordpress.org/core/2020/11/18/block-api-version-2/) ([#26098](https://github.com/WordPress/gutenberg/pull/26098)). + ## 0.18.0 (2020-10-30) ### Breaking Changes diff --git a/packages/create-block/README.md b/packages/create-block/README.md index 7d6db27e14a06f..f6b3f15d983b05 100644 --- a/packages/create-block/README.md +++ b/packages/create-block/README.md @@ -34,13 +34,13 @@ The following command generates PHP, JS and CSS code for registering a block. $ npx @wordpress/create-block [options] [slug] ``` -`[slug]` is optional. When provided it triggers the quick mode where it is used as the block slug used for its identification, the output location for scaffolded files, and the name of the WordPress plugin. The rest of the configuration is set to all default values unless overriden with some of the options listed below. +`[slug]` is optional. When provided it triggers the quick mode where it is used as the block slug used for its identification, the output location for scaffolded files, and the name of the WordPress plugin. The rest of the configuration is set to all default values unless overridden with some of the options listed below. Options: -```sh +```bash -V, --version output the version number --t, --template <name> block template type name, allowed values: "es5", "esnext" (default: "esnext") +-t, --template <name> block template type name, allowed values: "es5", "esnext", or the name of an external npm package (default: "esnext") --namespace <value> internal namespace for the block name --title <value> display title for the block --short-description <value> short description for the block @@ -74,9 +74,9 @@ $ npx @wordpress/create-block --help When you scaffold a block, you must provide at least a `slug` name, the `namespace` which usually corresponds to either the `theme` or `plugin` name, and the `category`. In most cases, we recommended pairing blocks with plugins rather than themes, because only using plugin ensures that all blocks still work when your theme changes. -## Available Commands +## Available Commands [ESNext template] -Inside that bootstrapped directory _(it doesn't apply to `es5` template)_, you can run several commands: +When bootstrapped with the `esnext` template (or any external template with `wpScripts` flag enabled), you can run several commands inside the directory: ```bash $ npm start @@ -114,6 +114,56 @@ $ npm run packages-update Updates WordPress packages to the latest version. [Learn more](/packages/scripts#packages-update). +## External Templates + +Since version `0.19.0` it is possible to use external templates hosted on npm. These packages need to contain `.mustache` files that will be used during the block scaffolding process. + +### Template Configuration + +It is mandatory to provide the main file for the package that returns a configuration object. It must containing at least `templatesPath` field with the path pointing to the location where template files live (nested folders are also supported). + +_Example:_ + +```js +module.exports = { + templatesPath: __dirname, +}; +``` + +It is also possible to override the default template configuration using the `defaultValues` field. + +_Example:_ + +```js +module.exports = { + defaultValues: { + slug: 'my-fantastic-block', + title: 'My fantastic block', + dashicon: 'palmtree', + version: '1.2.3', + }, + templatesPath: __dirname, +}; +``` + +The following configurable variables are used with the template files. Template authors can change default values to use when users don't provide their data: + +- `apiVersion` (default: `2`) +- `slug` (no default) +- `namespace` (default: `'create-block'`) +- `title` (no default) +- `description` (no default) +- `dashicon` (no default) +- `category` (default: `'widgets'`) +- `author` (default: `'The WordPress Contributors'`) +- `license` (default: `'GPL-2.0-or-later'`) +- `licenseURI` (default: `'https://www.gnu.org/licenses/gpl-2.0.html'`) +- `version` (default: `'0.1.0'`) +- `wpScripts` (default: `true`) +- `editorScript` (default: `'file:./build/index.js'`) +- `editorStyle` (default: `'file:./build/index.css'`) +- `style` (default: `'file:./build/style-index.css'`) + ## WP-CLI Another way of making a developer’s life easier is to use [WP-CLI](https://wp-cli.org), which provides a command-line interface for many actions you might perform on the WordPress instance. One of the commands `wp scaffold block` was used as the baseline for this tool and ES5 template in particular. diff --git a/packages/create-block/lib/index.js b/packages/create-block/lib/index.js index 082b21fee78883..d57c87c73b4491 100644 --- a/packages/create-block/lib/index.js +++ b/packages/create-block/lib/index.js @@ -34,7 +34,7 @@ program .arguments( '[slug]' ) .option( '-t, --template <name>', - 'block template type name, allowed values: "es5", "esnext"', + 'block template type name, allowed values: "es5", "esnext", or the name of an external npm package', 'esnext' ) .option( '--namespace <value>', 'internal namespace for the block name' ) @@ -91,6 +91,8 @@ program ( { name } ) => ! Object.keys( optionsValues ).includes( name ) ); + log.info( '' ); + log.info( "Let's customize your block:" ); const answers = await inquirer.prompt( prompts ); await scaffold( blockTemplate, { ...defaultValues, diff --git a/packages/create-block/lib/init-block-json.js b/packages/create-block/lib/init-block-json.js index 87905ea55bc43c..694414e73b547f 100644 --- a/packages/create-block/lib/init-block-json.js +++ b/packages/create-block/lib/init-block-json.js @@ -1,7 +1,7 @@ /** * External dependencies */ -const { isEmpty, omitBy } = require( 'lodash' ); +const { omitBy } = require( 'lodash' ); const { join } = require( 'path' ); const { writeFile } = require( 'fs' ).promises; @@ -11,6 +11,7 @@ const { writeFile } = require( 'fs' ).promises; const { info } = require( './log' ); module.exports = async ( { + apiVersion, slug, namespace, title, @@ -23,7 +24,6 @@ module.exports = async ( { style, } ) => { const outputFile = join( process.cwd(), slug, 'block.json' ); - info( '' ); info( 'Creating a "block.json" file.' ); await writeFile( @@ -31,6 +31,7 @@ module.exports = async ( { JSON.stringify( omitBy( { + apiVersion, name: namespace + '/' + slug, title, category, @@ -44,7 +45,7 @@ module.exports = async ( { editorStyle, style, }, - isEmpty + ( value ) => ! value ), null, '\t' diff --git a/packages/create-block/lib/init-wp-scripts.js b/packages/create-block/lib/init-wp-scripts.js index ac2ec27d6ff876..f375cd82398841 100644 --- a/packages/create-block/lib/init-wp-scripts.js +++ b/packages/create-block/lib/init-wp-scripts.js @@ -13,7 +13,7 @@ module.exports = async ( { slug } ) => { const cwd = join( process.cwd(), slug ); info( '' ); - info( 'Installing packages. It might take a couple of minutes.' ); + info( 'Installing packages. It might take a couple of minutes...' ); await command( 'npm install @wordpress/scripts --save-dev', { cwd, } ); diff --git a/packages/create-block/lib/scaffold.js b/packages/create-block/lib/scaffold.js index f7c6d60f305399..bbdd536c9af245 100644 --- a/packages/create-block/lib/scaffold.js +++ b/packages/create-block/lib/scaffold.js @@ -18,6 +18,7 @@ const { code, info, success } = require( './log' ); module.exports = async ( blockTemplate, { + apiVersion, namespace, slug, title, @@ -42,6 +43,7 @@ module.exports = async ( const { outputTemplates } = blockTemplate; const view = { + apiVersion, namespace, namespaceSnakeCase: snakeCase( namespace ), slug, diff --git a/packages/create-block/lib/templates.js b/packages/create-block/lib/templates.js index b570ab73d1144f..b926d20739aa35 100644 --- a/packages/create-block/lib/templates.js +++ b/packages/create-block/lib/templates.js @@ -1,15 +1,22 @@ /** * External dependencies */ +const { command } = require( 'execa' ); const glob = require( 'fast-glob' ); const { readFile } = require( 'fs' ).promises; -const { fromPairs } = require( 'lodash' ); +const { fromPairs, isObject } = require( 'lodash' ); const { join } = require( 'path' ); +/** + * WordPress dependencies + */ +const lazyImport = require( '@wordpress/lazy-import' ); + /** * Internal dependencies */ const CLIError = require( './cli-error' ); +const { info } = require( './log' ); const prompts = require( './prompts' ); const predefinedBlockTemplates = { @@ -19,6 +26,7 @@ const predefinedBlockTemplates = { title: 'ES5 Example', description: 'Example block written with ES5 standard and no JSX – no build step required.', + dashicon: 'smiley', wpScripts: false, editorScript: 'file:./index.js', editorStyle: 'file:./editor.css', @@ -31,12 +39,12 @@ const predefinedBlockTemplates = { title: 'ESNext Example', description: 'Example block written with ESNext standard and JSX support – build step required.', + dashicon: 'smiley', }, }, }; -const getOutputTemplates = async ( name ) => { - const outputTemplatesPath = join( __dirname, 'templates', name ); +const getOutputTemplates = async ( outputTemplatesPath ) => { const outputTemplatesFiles = await glob( '**/*.mustache', { cwd: outputTemplatesPath, dot: true, @@ -58,24 +66,60 @@ const getOutputTemplates = async ( name ) => { ); }; +const externalTemplateExists = async ( templateName ) => { + try { + await command( `npm view ${ templateName }` ); + } catch ( error ) { + return false; + } + return true; +}; + const getBlockTemplate = async ( templateName ) => { - if ( ! predefinedBlockTemplates[ templateName ] ) { + if ( predefinedBlockTemplates[ templateName ] ) { + return { + ...predefinedBlockTemplates[ templateName ], + outputTemplates: await getOutputTemplates( + join( __dirname, 'templates', templateName ) + ), + }; + } + if ( ! ( await externalTemplateExists( templateName ) ) ) { throw new CLIError( - `Invalid block template type name. Allowed values: ${ Object.keys( - predefinedBlockTemplates - ).join( ', ' ) }.` + `Invalid block template type name: "${ templateName }". Allowed values: ` + + Object.keys( predefinedBlockTemplates ) + .map( ( name ) => `"${ name }"` ) + .join( ', ' ) + + ', or an existing npm package name.' + ); + } + + try { + info( '' ); + info( 'Downloading template files. It might take some time...' ); + + const { defaultValues = {}, templatesPath } = await lazyImport( + templateName + ); + if ( ! isObject( defaultValues ) || ! templatesPath ) { + throw new Error(); + } + + return { + defaultValues, + outputTemplates: await getOutputTemplates( templatesPath ), + }; + } catch ( error ) { + throw new CLIError( + `Invalid template definition provided in "${ templateName }" package.` ); } - return { - ...predefinedBlockTemplates[ templateName ], - outputTemplates: await getOutputTemplates( templateName ), - }; }; const getDefaultValues = ( blockTemplate ) => { return { + apiVersion: 2, namespace: 'create-block', - dashicon: 'smiley', category: 'widgets', author: 'The WordPress Contributors', license: 'GPL-2.0-or-later', diff --git a/packages/create-block/lib/templates/es5/$slug.php.mustache b/packages/create-block/lib/templates/es5/$slug.php.mustache index 4692832fc576e1..452e52c23f3cc8 100644 --- a/packages/create-block/lib/templates/es5/$slug.php.mustache +++ b/packages/create-block/lib/templates/es5/$slug.php.mustache @@ -26,7 +26,7 @@ * @see https://developer.wordpress.org/block-editor/tutorials/block-tutorial/applying-styles-with-stylesheets/ */ function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() { - $dir = dirname( __FILE__ ); + $dir = __DIR__; $index_js = 'index.js'; wp_register_script( diff --git a/packages/create-block/lib/templates/es5/index.js.mustache b/packages/create-block/lib/templates/es5/index.js.mustache index b0486b43e4679d..318bd7f21dba17 100644 --- a/packages/create-block/lib/templates/es5/index.js.mustache +++ b/packages/create-block/lib/templates/es5/index.js.mustache @@ -20,12 +20,24 @@ */ var __ = wp.i18n.__; + /** + * This hook is used to mark the block wrapper element. + * + * @see https://developer.wordpress.org/block-editor/packages/packages-block-editor/#useBlockProps + */ + var useBlockProps = wp.blockEditor.useBlockProps; + /** * Every block starts by registering a new block type definition. * * @see https://developer.wordpress.org/block-editor/developers/block-api/#registering-a-block */ registerBlockType( '{{namespace}}/{{slug}}', { + /** + * @see https://make.wordpress.org/core/2020/11/18/block-api-version-2/ + */ + apiVersion: {{apiVersion}}, + /** * This is the display title for your block, which can be translated with `i18n` functions. * The block inserter will show this name. @@ -74,14 +86,12 @@ * * @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#edit * - * @param {Object} [props] Properties passed from the editor. - * * @return {WPElement} Element to render. */ - edit: function( props ) { + edit: function() { return el( 'p', - { className: props.className }, + useBlockProps(), __( '{{title}} – hello from the editor!', '{{textdomain}}' ) ); }, diff --git a/packages/create-block/lib/templates/es5/readme.txt.mustache b/packages/create-block/lib/templates/es5/readme.txt.mustache index 56262c0b4d2638..0836645a1f9a7e 100644 --- a/packages/create-block/lib/templates/es5/readme.txt.mustache +++ b/packages/create-block/lib/templates/es5/readme.txt.mustache @@ -3,8 +3,8 @@ Contributors: {{author}} {{/author}} Tags: block -Requires at least: 5.5.0 -Tested up to: 5.5.1 +Requires at least: 5.6.0 +Tested up to: 5.6.0 Stable tag: {{version}} Requires PHP: 7.0.0 {{#license}} diff --git a/packages/create-block/lib/templates/esnext/$slug.php.mustache b/packages/create-block/lib/templates/esnext/$slug.php.mustache index 5d4978a181b538..e168ee4adec448 100644 --- a/packages/create-block/lib/templates/esnext/$slug.php.mustache +++ b/packages/create-block/lib/templates/esnext/$slug.php.mustache @@ -26,7 +26,7 @@ * @see https://developer.wordpress.org/block-editor/tutorials/block-tutorial/applying-styles-with-stylesheets/ */ function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() { - $dir = dirname( __FILE__ ); + $dir = __DIR__; $script_asset_path = "$dir/build/index.asset.php"; if ( ! file_exists( $script_asset_path ) ) { diff --git a/packages/create-block/lib/templates/esnext/readme.txt.mustache b/packages/create-block/lib/templates/esnext/readme.txt.mustache index 56262c0b4d2638..0836645a1f9a7e 100644 --- a/packages/create-block/lib/templates/esnext/readme.txt.mustache +++ b/packages/create-block/lib/templates/esnext/readme.txt.mustache @@ -3,8 +3,8 @@ Contributors: {{author}} {{/author}} Tags: block -Requires at least: 5.5.0 -Tested up to: 5.5.1 +Requires at least: 5.6.0 +Tested up to: 5.6.0 Stable tag: {{version}} Requires PHP: 7.0.0 {{#license}} diff --git a/packages/create-block/lib/templates/esnext/src/edit.js.mustache b/packages/create-block/lib/templates/esnext/src/edit.js.mustache index 524ffa4f47a1b7..8ed2bb5d381980 100644 --- a/packages/create-block/lib/templates/esnext/src/edit.js.mustache +++ b/packages/create-block/lib/templates/esnext/src/edit.js.mustache @@ -5,6 +5,14 @@ */ import { __ } from '@wordpress/i18n'; +/** + * React hook that is used to mark the block wrapper element. + * It provides all the necessary props like the class name. + * + * @see https://developer.wordpress.org/block-editor/packages/packages-block-editor/#useBlockProps + */ +import { useBlockProps } from '@wordpress/block-editor'; + /** * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files. * Those files can contain any CSS code that gets applied to the editor. @@ -19,14 +27,11 @@ import './editor.scss'; * * @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#edit * - * @param {Object} [props] Properties passed from the editor. - * @param {string} [props.className] Class name generated for the block. - * * @return {WPElement} Element to render. */ -export default function Edit( { className } ) { +export default function Edit() { return ( - <p className={ className }> + <p { ...useBlockProps() }> { __( '{{title}} – hello from the editor!', '{{textdomain}}' ) } </p> ); diff --git a/packages/create-block/lib/templates/esnext/src/index.js.mustache b/packages/create-block/lib/templates/esnext/src/index.js.mustache index ef9236403bbf21..13ecd405a8be95 100644 --- a/packages/create-block/lib/templates/esnext/src/index.js.mustache +++ b/packages/create-block/lib/templates/esnext/src/index.js.mustache @@ -33,6 +33,11 @@ import save from './save'; * @see https://developer.wordpress.org/block-editor/developers/block-api/#registering-a-block */ registerBlockType( '{{namespace}}/{{slug}}', { + /** + * @see https://make.wordpress.org/core/2020/11/18/block-api-version-2/ + */ + apiVersion: {{apiVersion}}, + /** * This is the display title for your block, which can be translated with `i18n` functions. * The block inserter will show this name. diff --git a/packages/create-block/package.json b/packages/create-block/package.json index 6ed459029c066c..ba9a1b586a2179 100644 --- a/packages/create-block/package.json +++ b/packages/create-block/package.json @@ -31,6 +31,7 @@ "wp-create-block": "./index.js" }, "dependencies": { + "@wordpress/lazy-import": "file:../lazy-import", "chalk": "^4.0.0", "check-node-version": "^3.1.1", "commander": "^4.1.0", diff --git a/packages/data-controls/README.md b/packages/data-controls/README.md index d9a346956e00c9..20652cc9c3457e 100644 --- a/packages/data-controls/README.md +++ b/packages/data-controls/README.md @@ -64,14 +64,18 @@ import * as actions from './actions'; import * as resolvers from './resolvers'; registerStore( 'my-custom-store', { - reducer, - controls, - actions, - selectors, - resolvers, +reducer, +controls, +actions, +selectors, +resolvers, } ); ``` +_Parameters_ + +- _paths_ (unknown type): + _Returns_ - `Object`: An object for registering the default controls with the store. diff --git a/packages/data-controls/package.json b/packages/data-controls/package.json index 69aaf41acd1bc6..8d99260e487eeb 100644 --- a/packages/data-controls/package.json +++ b/packages/data-controls/package.json @@ -23,8 +23,10 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { + "@babel/runtime": "^7.12.5", "@wordpress/api-fetch": "file:../api-fetch", - "@wordpress/data": "file:../data" + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated" }, "publishConfig": { "access": "public" diff --git a/packages/data-controls/src/index.js b/packages/data-controls/src/index.js index 4c618e086d8855..baf191d7ac7ef5 100644 --- a/packages/data-controls/src/index.js +++ b/packages/data-controls/src/index.js @@ -3,8 +3,7 @@ */ import triggerFetch from '@wordpress/api-fetch'; import { controls as dataControls } from '@wordpress/data'; -// TODO: mark the deprecated controls after all Gutenberg usages are removed -// import deprecated from '@wordpress/deprecated'; +import deprecated from '@wordpress/deprecated'; /** * Dispatches a control action for triggering an api fetch call. @@ -39,9 +38,9 @@ export function apiFetch( request ) { * @param {Array} args Arguments passed without change to the `@wordpress/data` control. */ export function select( ...args ) { - // deprecated( '`select` control in `@wordpress/data-controls`', { - // alternative: 'built-in `resolveSelect` control in `@wordpress/data`', - // } ); + deprecated( '`select` control in `@wordpress/data-controls`', { + alternative: 'built-in `resolveSelect` control in `@wordpress/data`', + } ); return dataControls.resolveSelect( ...args ); } @@ -53,9 +52,9 @@ export function select( ...args ) { * @param {Array} args Arguments passed without change to the `@wordpress/data` control. */ export function syncSelect( ...args ) { - // deprecated( '`syncSelect` control in `@wordpress/data-controls`', { - // alternative: 'built-in `select` control in `@wordpress/data`', - // } ); + deprecated( '`syncSelect` control in `@wordpress/data-controls`', { + alternative: 'built-in `select` control in `@wordpress/data`', + } ); return dataControls.select( ...args ); } @@ -67,17 +66,44 @@ export function syncSelect( ...args ) { * @param {Array} args Arguments passed without change to the `@wordpress/data` control. */ export function dispatch( ...args ) { - // deprecated( '`dispatch` control in `@wordpress/data-controls`', { - // alternative: 'built-in `dispatch` control in `@wordpress/data`', - // } ); + deprecated( '`dispatch` control in `@wordpress/data-controls`', { + alternative: 'built-in `dispatch` control in `@wordpress/data`', + } ); return dataControls.dispatch( ...args ); } +/** + * Dispatches a control action for awaiting on a promise to be resolved. + * + * @param {Object} promise Promise to wait for. + * + * @example + * ```js + * import { __unstableAwaitPromise } from '@wordpress/data-controls'; + * + * // Action generator using apiFetch + * export function* myAction() { + * const promise = getItemsAsync(); + * const items = yield __unstableAwaitPromise( promise ); + * // do something with the items. + * } + * ``` + * + * @return {Object} The control descriptor. + */ +export const __unstableAwaitPromise = function ( promise ) { + return { + type: 'AWAIT_PROMISE', + promise, + }; +}; + /** * The default export is what you use to register the controls with your custom * store. * + * @param paths * @example * ```js * // WordPress dependencies @@ -91,18 +117,18 @@ export function dispatch( ...args ) { * import * as resolvers from './resolvers'; * * registerStore( 'my-custom-store', { - * reducer, - * controls, - * actions, - * selectors, - * resolvers, + * reducer, + * controls, + * actions, + * selectors, + * resolvers, * } ); * ``` - * * @return {Object} An object for registering the default controls with the - * store. + * store. */ export const controls = { + AWAIT_PROMISE: ( { promise } ) => promise, API_FETCH( { request } ) { return triggerFetch( request ); }, diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index 38a66378fb1d6d..3cef1e22d0850a 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -2,54 +2,66 @@ ## Unreleased +### New Features + +- Added new `register` function for registering a standard `@wordpress/data` store definition ([#26655](https://github.com/WordPress/gutenberg/pull/26655)). +- Added new `createReduxStore` factory function that creates a data store definition for the provided Redux store options to use with `register` function ([#26655](https://github.com/WordPress/gutenberg/pull/26655)). +- Extended `select` and `dispatch` functions to accept a data store definition as their first param in addition to a string-based store name value [#26655](https://github.com/WordPress/gutenberg/pull/26655)). +- Extended `useDispatch` hook to accept a data store definition as their first param in addition to a string-based store name value [#26655](https://github.com/WordPress/gutenberg/pull/26655)). + +### Deprecations + +- `registerGenericStore` has been deprecated. Use `register` instead. +- `registerStore` has been deprecated. Use `register` instead. + ## 4.6.0 (2019-06-12) ### New Feature -- Expose `useSelect` hook for usage in functional components. ([#15737](https://github.com/WordPress/gutenberg/pull/15737)) -- Expose `useDispatch` hook for usage in functional components. ([#15896](https://github.com/WordPress/gutenberg/pull/15896)) +- Expose `useSelect` hook for usage in functional components. ([#15737](https://github.com/WordPress/gutenberg/pull/15737)) +- Expose `useDispatch` hook for usage in functional components. ([#15896](https://github.com/WordPress/gutenberg/pull/15896)) ### Enhancements -- `withSelect` internally uses the new `useSelect` hook. ([#15737](https://github.com/WordPress/gutenberg/pull/15737). **Note:** This _could_ impact performance of code using `withSelect` in edge-cases. To avoid impact, memoize passed in `mapSelectToProps` callbacks or implement `useSelect` directly with dependencies. -- `withDispatch` internally uses a new `useDispatchWithMap` hook (an internal only api) ([#15896](https://github.com/WordPress/gutenberg/pull/15896)) +- `withSelect` internally uses the new `useSelect` hook. ([#15737](https://github.com/WordPress/gutenberg/pull/15737). **Note:** This _could_ impact performance of code using `withSelect` in edge-cases. To avoid impact, memoize passed in `mapSelectToProps` callbacks or implement `useSelect` directly with dependencies. +- `withDispatch` internally uses a new `useDispatchWithMap` hook (an internal only api) ([#15896](https://github.com/WordPress/gutenberg/pull/15896)) ## 4.5.0 (2019-05-21) ### Bug Fix -- Restore functionality of action-generators returning a Promise. Clarify intent and behaviour for `wp.data.dispatch` behaviour. Dispatch actions now always - return a promise ([#14830](https://github.com/WordPress/gutenberg/pull/14830) +- Restore functionality of action-generators returning a Promise. Clarify intent and behaviour for `dispatch` behaviour. Dispatch actions now always + return a promise ([#14830](https://github.com/WordPress/gutenberg/pull/14830) ### Enhancements -- Expose `hasResolver` property on returned selectors indicating whether the selector has a corresponding resolver. +- Expose `hasResolver` property on returned selectors indicating whether the selector has a corresponding resolver. ## 4.3.0 (2019-03-06) ### Enhancements -- The `registerStore` function now accepts an optional `initialState` option value. -- Introduce new `invalidateResolutionForStore` dispatch action for signalling to invalidate the resolution cache for an entire given store. -- Introduce new `invalidateResolutionForStoreSelector` dispatch action for signalling to invalidate the resolution cache for a store selector (and all variations of arguments on that selector). +- The `registerStore` function now accepts an optional `initialState` option value. +- Introduce new `invalidateResolutionForStore` dispatch action for signalling to invalidate the resolution cache for an entire given store. +- Introduce new `invalidateResolutionForStoreSelector` dispatch action for signalling to invalidate the resolution cache for a store selector (and all variations of arguments on that selector). ### Bug Fix -- Resolves issue in the persistence plugin where passing `persist` as an array of reducer keys would wrongly replace state values for the unpersisted reducer keys. -- Restores a behavior in the persistence plugin where a default state provided as an object will be deeply merged as a base for the persisted value. This allows for a developer to include additional new keys in a persisted value default in future iterations of their store. +- Resolves issue in the persistence plugin where passing `persist` as an array of reducer keys would wrongly replace state values for the unpersisted reducer keys. +- Restores a behavior in the persistence plugin where a default state provided as an object will be deeply merged as a base for the persisted value. This allows for a developer to include additional new keys in a persisted value default in future iterations of their store. ## 4.2.0 (2019-01-03) ### Enhancements -- Optimized performance of selector execution (~511% improvement) +- Optimized performance of selector execution (~511% improvement) ## 4.1.0 (2018-12-12) ### New Feature -- `withDispatch`'s `mapDispatchToProps` function takes the `registry` object as the 3rd param ([#11851](https://github.com/WordPress/gutenberg/pull/11851)). -- `withSelect`'s `mapSelectToProps` function takes the `registry` object as the 3rd param ([#11851](https://github.com/WordPress/gutenberg/pull/11851)). +- `withDispatch`'s `mapDispatchToProps` function takes the `registry` object as the 3rd param ([#11851](https://github.com/WordPress/gutenberg/pull/11851)). +- `withSelect`'s `mapSelectToProps` function takes the `registry` object as the 3rd param ([#11851](https://github.com/WordPress/gutenberg/pull/11851)). ## 4.0.1 (2018-11-20) @@ -57,14 +69,14 @@ ### Breaking Changes -- `registry.registerReducer` has been removed. Use `registry.registerStore` instead. -- `registry.registerSelectors` has been removed. Use `registry.registerStore` instead. -- `registry.registerActions` has been removed. Use `registry.registerStore` instead. -- `registry.registerResolvers` has been removed. Use `registry.registerStore` instead. +- `registry.registerReducer` has been removed. Use `registry.registerStore` instead. +- `registry.registerSelectors` has been removed. Use `registry.registerStore` instead. +- `registry.registerActions` has been removed. Use `registry.registerStore` instead. +- `registry.registerResolvers` has been removed. Use `registry.registerStore` instead. ### Bug Fix -- Resolve an issue where `withSelect`'s `mapSelectToProps` would not be rerun if the wrapped component had incurred a store change during its mount lifecycle. +- Resolve an issue where `withSelect`'s `mapSelectToProps` would not be rerun if the wrapped component had incurred a store change during its mount lifecycle. ## 3.1.2 (2018-11-09) @@ -74,26 +86,26 @@ ### New Features -- `registry.registerGenericStore` has been added to support integration with existing data systems. +- `registry.registerGenericStore` has been added to support integration with existing data systems. ### Deprecations -- `registry.registerReducer` has been deprecated. Use `registry.registerStore` instead. -- `registry.registerSelectors` has been deprecated. Use `registry.registerStore` instead. -- `registry.registerActions` has been deprecated. Use `registry.registerStore` instead. -- `registry.registerResolvers` has been deprecated. Use `registry.registerStore` instead. +- `registry.registerReducer` has been deprecated. Use `registry.registerStore` instead. +- `registry.registerSelectors` has been deprecated. Use `registry.registerStore` instead. +- `registry.registerActions` has been deprecated. Use `registry.registerStore` instead. +- `registry.registerResolvers` has been deprecated. Use `registry.registerStore` instead. ## 3.0.1 (2018-10-30) ### Internal -- Replace Redux implementation of `combineReducers` with in-place-compatible `turbo-combine-reducers`. +- Replace Redux implementation of `combineReducers` with in-place-compatible `turbo-combine-reducers`. ## 3.0.0 (2018-10-29) ### Breaking Changes -- Writing resolvers as async generators has been removed. Use the controls plugin instead. +- Writing resolvers as async generators has been removed. Use the controls plugin instead. ## 2.1.4 (2018-10-19) @@ -103,25 +115,25 @@ ### New Features -- Adding support for using controls in resolvers using the controls plugin. +- Adding support for using controls in resolvers using the controls plugin. ### Polish -- Updated `redux` dependency to the latest version. +- Updated `redux` dependency to the latest version. ### Deprecations -- Writing resolvers as async generators has been deprecated. Use the controls plugin instead. +- Writing resolvers as async generators has been deprecated. Use the controls plugin instead. ### Bug Fixes -- Fix the promise middleware in Firefox. +- Fix the promise middleware in Firefox. ## 2.0.0 (2018-09-05) ### Breaking Change -- The `withRehdyration` function is removed. Use the persistence plugin instead. -- The `loadAndPersist` function is removed. Use the persistence plugin instead. -- `restrictPersistence`, `setPersistenceStorage` and `setupPersistence` functions have been removed. Please use the data persistence plugin instead. -- Change how required built-ins are polyfilled with Babel 7 ([#9171](https://github.com/WordPress/gutenberg/pull/9171)). If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. +- The `withRehdyration` function is removed. Use the persistence plugin instead. +- The `loadAndPersist` function is removed. Use the persistence plugin instead. +- `restrictPersistence`, `setPersistenceStorage` and `setupPersistence` functions have been removed. Please use the data persistence plugin instead. +- Change how required built-ins are polyfilled with Babel 7 ([#9171](https://github.com/WordPress/gutenberg/pull/9171)). If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. diff --git a/packages/data/README.md b/packages/data/README.md index 8574f850247d41..ab6ebf37297b32 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -16,11 +16,11 @@ _This package assumes that your code will run in an **ES2015+** environment. If ## Registering a Store -Use the `registerStore` function to add your own store to the centralized data registry. This function accepts two arguments: a name to identify the module, and an object with values describing how your state is represented, modified, and accessed. At a minimum, you must provide a reducer function describing the shape of your state and how it changes in response to actions dispatched to the store. +Use the `register` function to add your own store to the centralized data registry. This function accepts one argument – a store definition object that can be created with `createReduxStore` factory function. `createReduxStore` accepts two arguments: a name to identify the module, and an object with values describing how your state is represented, modified, and accessed. At a minimum, you must provide a reducer function describing the shape of your state and how it changes in response to actions dispatched to the store. ```js -const { apiFetch } = wp; -const { registerStore } = wp.data; +import apiFetch from '@wordpress/api-fetch'; +import { createReduxStore, register } from '@wordpress/data'; const DEFAULT_STATE = { prices: {}, @@ -51,7 +51,7 @@ const actions = { }, }; -registerStore( 'my-shop', { +const store = createReduxStore( 'my-shop', { reducer( state = DEFAULT_STATE, action ) { switch ( action.type ) { case 'SET_PRICE': @@ -80,7 +80,7 @@ registerStore( 'my-shop', { const { prices, discountPercent } = state; const price = prices[ item ]; - return price * ( 1 - ( 0.01 * discountPercent ) ); + return price * ( 1 - 0.01 * discountPercent ); }, }, @@ -91,25 +91,29 @@ registerStore( 'my-shop', { }, resolvers: { - * getPrice( item ) { + *getPrice( item ) { const path = '/wp/v2/prices/' + item; const price = yield actions.fetchFromAPI( path ); return actions.setPrice( item, price ); }, }, } ); + +register( store ); ``` -The return value of `registerStore` is a [Redux-like store object](https://redux.js.org/basics/store) with the following methods: +The return value of `createReduxStore` is the `WPDataStore` object that contains two properties: -- `store.getState()`: Returns the state value of the registered reducer - - _Redux parallel:_ [`getState`](https://redux.js.org/api/store#getstate) -- `store.subscribe( listener: Function )`: Registers a function called any time the value of state changes. - - _Redux parallel:_ [`subscribe`](https://redux.js.org/api/store#subscribelistener) -- `store.dispatch( action: Object )`: Given an action object, calls the registered reducer and updates the state value. - - _Redux parallel:_ [`dispatch`](https://redux.js.org/api/store#dispatchaction) +- `name` (`string`) – the name of the store +- `instantiate` (`Function`) - it returns a [Redux-like store object](https://redux.js.org/basics/store) with the following methods: + - `getState()`: Returns the state value of the registered reducer + - _Redux parallel:_ [`getState`](https://redux.js.org/api/store#getstate) + - `subscribe( listener: Function )`: Registers a function called any time the value of state changes. + - _Redux parallel:_ [`subscribe`](https://redux.js.org/api/store#subscribelistener) + - `dispatch( action: Object )`: Given an action object, calls the registered reducer and updates the state value. + - _Redux parallel:_ [`dispatch`](https://redux.js.org/api/store#dispatchaction) -### Options +### Redux Store Options #### `reducer` @@ -151,7 +155,7 @@ The `@wordpress/data` module offers a more advanced and generic interface for th - Behaves as Redux [`subscribe`](https://redux.js.org/api/store#subscribelistener) with the following differences: - Doesn't have to implement an unsubscribe, since the registry never uses it. - \- Only has to support one listener (the registry). + \- Only has to support one listener (the registry). By implementing the above interface for your custom store, you gain the benefits of using the registry and the `withSelect` and `withDispatch` higher order components in your application code. This provides seamless integration with existing and alternative data systems. @@ -164,20 +168,27 @@ import existingSelectors from './existing-app/selectors'; import existingActions from './existing-app/actions'; import createStore from './existing-app/store'; -const { registerGenericStore } = wp.data; +import { registerGenericStore } from 'wordpress/data'; const reduxStore = createStore(); -const mappedSelectors = Object.keys( existingSelectors ).reduce( ( acc, selectorKey ) => { - acc[ selectorKey ] = ( ...args ) => - existingSelectors[ selectorKey ]( reduxStore.getState(), ...args ); - return acc; -}, {} ); - -const mappedActions = Object.keys( existingActions ).reduce( ( acc, actionKey ) => { - acc[ actionKey ] = ( ...args ) => reduxStore.dispatch( existingActions[ actionKey ]( ...args ) ); - return acc; -}, {} ); +const mappedSelectors = Object.keys( existingSelectors ).reduce( + ( acc, selectorKey ) => { + acc[ selectorKey ] = ( ...args ) => + existingSelectors[ selectorKey ]( reduxStore.getState(), ...args ); + return acc; + }, + {} +); + +const mappedActions = Object.keys( existingActions ).reduce( + ( acc, actionKey ) => { + acc[ actionKey ] = ( ...args ) => + reduxStore.dispatch( existingActions[ actionKey ]( ...args ) ); + return acc; + }, + {} +); const genericStore = { getSelectors() { @@ -197,11 +208,11 @@ It is also possible to implement a completely custom store from scratch: _Example:_ ```js -const { registerGenericStore } = wp.data; +import { registerGenericStore } from '@wordpress/data'; function createCustomStore() { let storeChanged = () => {}; - const prices = { hammer: 7.50 }; + const prices = { hammer: 7.5 }; const selectors = { getPrice( itemName ) { @@ -225,7 +236,7 @@ function createCustomStore() { }, subscribe( listener ) { storeChanged = listener; - } + }, }; } @@ -238,6 +249,8 @@ The data module shares many of the same [core principles](https://redux.js.org/i The [higher-order components](#higher-order-components) were created to complement this distinction. The intention with splitting `withSelect` and `withDispatch` — where in React Redux they are combined under `connect` as `mapStateToProps` and `mapDispatchToProps` arguments — is to more accurately reflect that dispatch is not dependent upon a subscription to state changes, and to allow for state-derived values to be used in `withDispatch` (via [higher-order component composition](/packages/compose/README.md)). +The data module also has built-in solutions for handling asynchronous side-effects, through [resolvers](#resolvers) and [controls](#controls). These differ slightly from [standard redux async solutions](https://redux.js.org/advanced/async-actions) like [`redux-thunk`](https://github.com/gaearon/redux-thunk) or [`redux-saga`](https://redux-saga.js.org/). + Specific implementation differences from Redux and React Redux: - In Redux, a `subscribe` listener is called on every dispatch, regardless of whether the value of state has changed. @@ -299,7 +312,7 @@ reducing functions into a single reducing function you can pass to registerReduc _Usage_ ```js -const { combineReducers, registerStore } = wp.data; +import { combineReducers, createReduxStore, register } from '@wordpress/data'; const prices = ( state = {}, action ) => { return action.type === 'SET_PRICE' ? @@ -316,12 +329,13 @@ const discountPercent = ( state = 0, action ) => { state; }; -registerStore( 'my-shop', { +const store = createReduxStore( 'my-shop', { reducer: combineReducers( { prices, discountPercent, } ), } ); +register( store ); ``` _Parameters_ @@ -336,6 +350,33 @@ _Returns_ Undocumented declaration. +<a name="createReduxStore" href="#createReduxStore">#</a> **createReduxStore** + +Creates a data store definition for the provided Redux store options containing +properties describing reducer, actions, selectors, controls and resolvers. + +_Usage_ + +```js +import { createReduxStore } from '@wordpress/data'; + +const store = createReduxStore( 'demo', { + reducer: ( state = 'OK' ) => state, + selectors: { + getValue: ( state ) => state, + }, +} ); +``` + +_Parameters_ + +- _key_ `string`: Unique namespace identifier. +- _options_ `WPDataReduxStoreConfig`: Registered store options, with properties describing reducer, actions, selectors, and resolvers. + +_Returns_ + +- `WPDataStore`: Store Object. + <a name="createRegistry" href="#createRegistry">#</a> **createRegistry** Creates a new store registry, given an optional object of initial store @@ -436,14 +477,14 @@ they are called. _Usage_ ```js -const { dispatch } = wp.data; +import { dispatch } from '@wordpress/data'; dispatch( 'my-shop' ).setPrice( 'hammer', 9.75 ); ``` _Parameters_ -- _name_ `string`: Store name. +- _storeNameOrDefinition_ `(string|WPDataStore)`: Unique namespace identifier for the store or the store definition. _Returns_ @@ -461,8 +502,32 @@ _Type_ - `Object` +<a name="register" href="#register">#</a> **register** + +Registers a standard `@wordpress/data` store definition. + +_Usage_ + +```js +import { createReduxStore, register } from '@wordpress/data'; + +const store = createReduxStore( 'demo', { + reducer: ( state = 'OK' ) => state, + selectors: { + getValue: ( state ) => state, + }, +} ); +register( store ); +``` + +_Parameters_ + +- _store_ `WPDataStore`: Store definition. + <a name="registerGenericStore" href="#registerGenericStore">#</a> **registerGenericStore** +> **Deprecated** Use `register` instead. + Registers a generic store. _Parameters_ @@ -472,11 +537,13 @@ _Parameters_ <a name="registerStore" href="#registerStore">#</a> **registerStore** +> **Deprecated** Use `register` instead. + Registers a standard `@wordpress/data` store. _Parameters_ -- _reducerKey_ `string`: Reducer key. +- _storeName_ `string`: Unique namespace identifier for the store. - _options_ `Object`: Store description (reducer, actions, selectors, resolvers). _Returns_ @@ -494,11 +561,11 @@ You can read more about the react context api here: _Usage_ ```js -const { +import { RegistryProvider, RegistryConsumer, createRegistry -} = wp.data; +} from '@wordpress/data'; const registry = createRegistry( {} ); @@ -526,21 +593,21 @@ example. <a name="select" href="#select">#</a> **select** -Given the name of a registered store, returns an object of the store's selectors. +Given the name or definition of a registered store, returns an object of the store's selectors. The selector functions are been pre-bound to pass the current state automatically. As a consumer, you need only pass arguments of the selector, if applicable. _Usage_ ```js -const { select } = wp.data; +import { select } from '@wordpress/data'; select( 'my-shop' ).getPrice( 'hammer' ); ``` _Parameters_ -- _name_ `string`: Store name. +- _storeNameOrDefinition_ `(string|WPDataStore)`: Unique namespace identifier for the store or the store definition. _Returns_ @@ -555,7 +622,7 @@ function used to stop the subscription. _Usage_ ```js -const { subscribe } = wp.data; +import { subscribe } from '@wordpress/data'; const unsubscribe = subscribe( () => { // You could use this opportunity to test whether the derived result of a @@ -594,8 +661,8 @@ the server via the `useSelect` hook to use in combination with the dispatch action. ```jsx -const { useDispatch, useSelect } = wp.data; -const { useCallback } = wp.element; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useCallback } from '@wordpress/element'; function Button( { onClick, children } ) { return <button type="button" onClick={ onClick }>{ children }</button> @@ -621,7 +688,7 @@ const SaleButton = ( { children } ) => { _Parameters_ -- _storeName_ `[string]`: Optionally provide the name of the store from which to retrieve action creators. If not provided, the registry.dispatch function is returned instead. +- _storeNameOrDefinition_ `[(string|WPDataStore)]`: Optionally provide the name of the store or its definition from which to retrieve action creators. If not provided, the registry.dispatch function is returned instead. _Returns_ @@ -638,18 +705,18 @@ this hook. It acts similarly to the `useContext` react hook. Note: Generally speaking, `useRegistry` is a low level hook that in most cases -won't be needed for implementation. Most interactions with the wp.data api -can be performed via the `useSelect` hook, or the `withSelect` and +won't be needed for implementation. Most interactions with the `@wordpress/data` +API can be performed via the `useSelect` hook, or the `withSelect` and `withDispatch` higher order components. _Usage_ ```js -const { +import { RegistryProvider, createRegistry, useRegistry, -} = wp.data +} from '@wordpress/data'; const registry = createRegistry( {} ); @@ -680,7 +747,7 @@ In general, this custom React hook follows the _Usage_ ```js -const { useSelect } = wp.data; +import { useSelect } from '@wordpress/data'; function HammerPriceDisplay( { currency } ) { const price = useSelect( ( select ) => { @@ -724,7 +791,7 @@ function Button( { onClick, children } ) { return <button type="button" onClick={ onClick }>{ children }</button>; } -const { withDispatch } = wp.data; +import { withDispatch } from '@wordpress/data'; const SaleButton = withDispatch( ( dispatch, ownProps ) => { const { startSale } = dispatch( 'my-shop' ); @@ -760,7 +827,7 @@ function Button( { onClick, children } ) { return <button type="button" onClick={ onClick }>{ children }</button>; } -const { withDispatch } = wp.data; +import { withDispatch } from '@wordpress/data'; const SaleButton = withDispatch( ( dispatch, ownProps, { select } ) => { // Stock number changes frequently. @@ -812,6 +879,8 @@ selectors. _Usage_ ```js +import { withSelect } from '@wordpress/data'; + function PriceDisplay( { price, currency } ) { return new Intl.NumberFormat( 'en-US', { style: 'currency', @@ -819,8 +888,6 @@ function PriceDisplay( { price, currency } ) { } ).format( price ); } -const { withSelect } = wp.data; - const HammerPriceDisplay = withSelect( ( select, ownProps ) => { const { getPrice } = select( 'my-shop' ); const { currency } = ownProps; diff --git a/packages/data/package.json b/packages/data/package.json index 41679972cf9918..41fe0f021ca0d1 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -24,7 +24,7 @@ "react-native": "src/index", "sideEffects": false, "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/compose": "file:../compose", "@wordpress/deprecated": "file:../deprecated", "@wordpress/element": "file:../element", diff --git a/packages/data/src/components/registry-provider/context.js b/packages/data/src/components/registry-provider/context.js index 157225a7c7888b..e784f4fd5c2810 100644 --- a/packages/data/src/components/registry-provider/context.js +++ b/packages/data/src/components/registry-provider/context.js @@ -21,11 +21,11 @@ const { Consumer, Provider } = Context; * * @example * ```js - * const { + * import { * RegistryProvider, * RegistryConsumer, * createRegistry - * } = wp.data; + * } from '@wordpress/data'; * * const registry = createRegistry( {} ); * diff --git a/packages/data/src/components/registry-provider/use-registry.js b/packages/data/src/components/registry-provider/use-registry.js index 846cb5627fd2f3..b05fe35b5554da 100644 --- a/packages/data/src/components/registry-provider/use-registry.js +++ b/packages/data/src/components/registry-provider/use-registry.js @@ -18,17 +18,17 @@ import { Context } from './context'; * It acts similarly to the `useContext` react hook. * * Note: Generally speaking, `useRegistry` is a low level hook that in most cases - * won't be needed for implementation. Most interactions with the wp.data api - * can be performed via the `useSelect` hook, or the `withSelect` and + * won't be needed for implementation. Most interactions with the `@wordpress/data` + * API can be performed via the `useSelect` hook, or the `withSelect` and * `withDispatch` higher order components. * * @example * ```js - * const { + * import { * RegistryProvider, * createRegistry, * useRegistry, - * } = wp.data + * } from '@wordpress/data'; * * const registry = createRegistry( {} ); * diff --git a/packages/data/src/components/use-dispatch/test/use-dispatch.js b/packages/data/src/components/use-dispatch/test/use-dispatch.js index 226c7d80e51ea7..1bae101204b91b 100644 --- a/packages/data/src/components/use-dispatch/test/use-dispatch.js +++ b/packages/data/src/components/use-dispatch/test/use-dispatch.js @@ -7,6 +7,7 @@ import TestRenderer, { act } from 'react-test-renderer'; * Internal dependencies */ import useDispatch from '../use-dispatch'; +import createReduxStore from '../../../redux-store'; import { createRegistry } from '../../../registry'; import { RegistryProvider } from '../../registry-provider'; @@ -47,6 +48,41 @@ describe( 'useDispatch', () => { ); } ); it( 'returns expected action creators from store for given storeName', () => { + const noop = () => ( { type: '__INERT__' } ); + const testAction = jest.fn().mockImplementation( noop ); + const store = createReduxStore( 'demoStore', { + reducer: ( state ) => state, + actions: { + foo: testAction, + }, + } ); + registry.register( store ); + + const TestComponent = () => { + const { foo } = useDispatch( store ); + return <button onClick={ foo } />; + }; + + let testRenderer; + + act( () => { + testRenderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <TestComponent /> + </RegistryProvider> + ); + } ); + + const testInstance = testRenderer.root; + + act( () => { + testInstance.findByType( 'button' ).props.onClick(); + } ); + + expect( testAction ).toHaveBeenCalledTimes( 1 ); + } ); + + it( 'returns expected action creators from store for given store definition', () => { const noop = () => ( { type: '__INERT__' } ); const testAction = jest.fn().mockImplementation( noop ); registry.registerStore( 'demoStore', { @@ -78,6 +114,7 @@ describe( 'useDispatch', () => { expect( testAction ).toHaveBeenCalledTimes( 1 ); } ); + it( 'returns dispatch from correct registry if registries change', () => { const reducer = ( state ) => state; const noop = () => ( { type: '__INERT__' } ); diff --git a/packages/data/src/components/use-dispatch/use-dispatch.js b/packages/data/src/components/use-dispatch/use-dispatch.js index 9add695434be11..5d0fc86883269b 100644 --- a/packages/data/src/components/use-dispatch/use-dispatch.js +++ b/packages/data/src/components/use-dispatch/use-dispatch.js @@ -3,16 +3,19 @@ */ import useRegistry from '../registry-provider/use-registry'; +/** @typedef {import('./types').WPDataStore} WPDataStore */ + /** * A custom react hook returning the current registry dispatch actions creators. * * Note: The component using this hook must be within the context of a * RegistryProvider. * - * @param {string} [storeName] Optionally provide the name of the store from - * which to retrieve action creators. If not - * provided, the registry.dispatch function is - * returned instead. + * @param {string|WPDataStore} [storeNameOrDefinition] Optionally provide the name of the + * store or its definition from which to + * retrieve action creators. If not + * provided, the registry.dispatch + * function is returned instead. * * @example * This illustrates a pattern where you may need to retrieve dynamic data from @@ -20,8 +23,8 @@ import useRegistry from '../registry-provider/use-registry'; * action. * * ```jsx - * const { useDispatch, useSelect } = wp.data; - * const { useCallback } = wp.element; + * import { useDispatch, useSelect } from '@wordpress/data'; + * import { useCallback } from '@wordpress/element'; * * function Button( { onClick, children } ) { * return <button type="button" onClick={ onClick }>{ children }</button> @@ -46,9 +49,11 @@ import useRegistry from '../registry-provider/use-registry'; * ``` * @return {Function} A custom react hook. */ -const useDispatch = ( storeName ) => { +const useDispatch = ( storeNameOrDefinition ) => { const { dispatch } = useRegistry(); - return storeName === void 0 ? dispatch : dispatch( storeName ); + return storeNameOrDefinition === void 0 + ? dispatch + : dispatch( storeNameOrDefinition ); }; export default useDispatch; diff --git a/packages/data/src/components/use-select/index.js b/packages/data/src/components/use-select/index.js index 0bcede5c9d7777..741ee366742e5b 100644 --- a/packages/data/src/components/use-select/index.js +++ b/packages/data/src/components/use-select/index.js @@ -13,6 +13,7 @@ import { useCallback, useEffect, useReducer, + useMemo, } from '@wordpress/element'; import isShallowEqual from '@wordpress/is-shallow-equal'; @@ -53,7 +54,7 @@ const renderQueue = createQueue(); * * @example * ```js - * const { useSelect } = wp.data; + * import { useSelect } from '@wordpress/data'; * * function HammerPriceDisplay( { currency } ) { * const price = useSelect( ( select ) => { @@ -94,6 +95,23 @@ export default function useSelect( _mapSelect, deps ) { const latestMapOutputError = useRef(); const isMountedAndNotUnsubscribing = useRef(); + // Keep track of the stores being selected in the mapSelect function, + // and only subscribe to those stores later. + const listeningStores = useRef( [] ); + const trapSelect = useCallback( + ( callback ) => + registry.__experimentalMarkListeningStores( + callback, + listeningStores + ), + [ registry ] + ); + + // Generate a "flag" for used in the effect dependency array. + // It's different than just using `mapSelect` since deps could be undefined, + // in that case, we would still want to memoize it. + const depsChangedFlag = useMemo( () => ( {} ), deps || [] ); + let mapOutput; try { @@ -101,7 +119,9 @@ export default function useSelect( _mapSelect, deps ) { latestMapSelect.current !== mapSelect || latestMapOutputError.current ) { - mapOutput = mapSelect( registry.select, registry ); + mapOutput = trapSelect( () => + mapSelect( registry.select, registry ) + ); } else { mapOutput = latestMapOutput.current; } @@ -140,10 +160,10 @@ export default function useSelect( _mapSelect, deps ) { const onStoreChange = () => { if ( isMountedAndNotUnsubscribing.current ) { try { - const newMapOutput = latestMapSelect.current( - registry.select, - registry + const newMapOutput = trapSelect( () => + latestMapSelect.current( registry.select, registry ) ); + if ( isShallowEqual( latestMapOutput.current, newMapOutput ) ) { @@ -165,20 +185,25 @@ export default function useSelect( _mapSelect, deps ) { onStoreChange(); } - const unsubscribe = registry.subscribe( () => { + const onChange = () => { if ( latestIsAsync.current ) { renderQueue.add( queueContext, onStoreChange ); } else { onStoreChange(); } - } ); + }; + + const unsubscribers = listeningStores.current.map( ( storeName ) => + registry.__experimentalSubscribeStore( storeName, onChange ) + ); return () => { isMountedAndNotUnsubscribing.current = false; - unsubscribe(); + // The return value of the subscribe function could be undefined if the store is a custom generic store. + unsubscribers.forEach( ( unsubscribe ) => unsubscribe?.() ); renderQueue.flush( queueContext ); }; - }, [ registry ] ); + }, [ registry, trapSelect, depsChangedFlag ] ); return mapOutput; } diff --git a/packages/data/src/components/use-select/test/index.js b/packages/data/src/components/use-select/test/index.js index 78a81ba3d47986..4dbfe576a2f5ef 100644 --- a/packages/data/src/components/use-select/test/index.js +++ b/packages/data/src/components/use-select/test/index.js @@ -3,10 +3,16 @@ */ import TestRenderer, { act } from 'react-test-renderer'; +/** + * WordPress dependencies + */ +import { useState, useReducer } from '@wordpress/element'; + /** * Internal dependencies */ import { createRegistry } from '../../../registry'; +import { createRegistrySelector } from '../../../factory'; import { RegistryProvider } from '../../registry-provider'; import useSelect from '../index'; @@ -110,7 +116,6 @@ describe( 'useSelect', () => { } ); // rerender with dependency changed - // rerender with non dependency changed act( () => { renderer.update( <RegistryProvider value={ registry }> @@ -120,7 +125,7 @@ describe( 'useSelect', () => { } ); expect( selectSpyFoo ).toHaveBeenCalledTimes( 2 ); - expect( selectSpyBar ).toHaveBeenCalledTimes( 1 ); + expect( selectSpyBar ).toHaveBeenCalledTimes( 2 ); expect( TestComponent ).toHaveBeenCalledTimes( 3 ); // ensure expected state was rendered @@ -133,23 +138,22 @@ describe( 'useSelect', () => { const data = useSelect( mapSelectSpy, [] ); return <div data={ data } />; }; - let subscribedSpy, TestComponent; + let TestComponent; const mapSelectSpy = jest.fn( ( select ) => select( 'testStore' ).testSelector() ); const selectorSpy = jest.fn(); - const subscribeCallback = ( subscription ) => { - subscribedSpy = subscription; - }; beforeEach( () => { registry.registerStore( 'testStore', { - reducer: () => null, + actions: { + forceUpdate: () => ( { type: 'FORCE_UPDATE' } ), + }, + reducer: ( state = {} ) => ( { ...state } ), selectors: { testSelector: selectorSpy, }, } ); - registry.subscribe = subscribeCallback; TestComponent = getComponent( mapSelectSpy ); } ); afterEach( () => { @@ -194,7 +198,7 @@ describe( 'useSelect', () => { // subscription which should in turn trigger a re-render. act( () => { selectorSpy.mockReturnValue( valueB ); - subscribedSpy(); + registry.dispatch( 'testStore' ).forceUpdate(); } ); expect( testInstance.findByType( 'div' ).props.data ).toEqual( valueB @@ -203,4 +207,583 @@ describe( 'useSelect', () => { } ); } ); + + describe( 're-calls the selector as minimal times as possible', () => { + const counterStore = { + actions: { + increment: () => ( { type: 'INCREMENT' } ), + }, + reducer: ( state, action ) => { + if ( ! state ) { + return { counter: 0 }; + } + if ( action?.type === 'INCREMENT' ) { + return { counter: state.counter + 1 }; + } + return state; + }, + selectors: { + getCounter: ( state ) => state.counter, + }, + }; + + it( 'only calls the selectors it has selected', () => { + registry.registerStore( 'store-1', counterStore ); + registry.registerStore( 'store-2', counterStore ); + + let renderer; + + const selectCount1 = jest.fn(); + const selectCount2 = jest.fn(); + + const TestComponent = jest.fn( () => { + const count1 = useSelect( + ( select ) => + selectCount1() || select( 'store-1' ).getCounter(), + [] + ); + useSelect( + ( select ) => + selectCount2() || select( 'store-2' ).getCounter(), + [] + ); + + return <div data={ count1 } />; + } ); + + act( () => { + renderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <TestComponent /> + </RegistryProvider> + ); + } ); + + const testInstance = renderer.root; + + expect( selectCount1 ).toHaveBeenCalledTimes( 2 ); + expect( selectCount2 ).toHaveBeenCalledTimes( 2 ); + expect( TestComponent ).toHaveBeenCalledTimes( 1 ); + expect( testInstance.findByType( 'div' ).props.data ).toBe( 0 ); + + act( () => { + registry.dispatch( 'store-2' ).increment(); + } ); + + expect( selectCount1 ).toHaveBeenCalledTimes( 2 ); + expect( selectCount2 ).toHaveBeenCalledTimes( 3 ); + expect( TestComponent ).toHaveBeenCalledTimes( 2 ); + expect( testInstance.findByType( 'div' ).props.data ).toBe( 0 ); + + act( () => { + registry.dispatch( 'store-1' ).increment(); + } ); + + expect( selectCount1 ).toHaveBeenCalledTimes( 3 ); + expect( selectCount2 ).toHaveBeenCalledTimes( 3 ); + expect( TestComponent ).toHaveBeenCalledTimes( 3 ); + expect( testInstance.findByType( 'div' ).props.data ).toBe( 1 ); + + // Test if the unsubscribers get called correctly. + renderer.unmount(); + } ); + + it( 'can subscribe to multiple stores at once', () => { + registry.registerStore( 'store-1', counterStore ); + registry.registerStore( 'store-2', counterStore ); + registry.registerStore( 'store-3', counterStore ); + + let renderer; + + const selectCount1And2 = jest.fn(); + + const TestComponent = jest.fn( () => { + const { count1, count2 } = useSelect( + ( select ) => + selectCount1And2() || { + count1: select( 'store-1' ).getCounter(), + count2: select( 'store-2' ).getCounter(), + }, + [] + ); + + return <div data={ { count1, count2 } } />; + } ); + + act( () => { + renderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <TestComponent /> + </RegistryProvider> + ); + } ); + + const testInstance = renderer.root; + + expect( selectCount1And2 ).toHaveBeenCalledTimes( 2 ); + expect( testInstance.findByType( 'div' ).props.data ).toEqual( { + count1: 0, + count2: 0, + } ); + + act( () => { + registry.dispatch( 'store-2' ).increment(); + } ); + + expect( selectCount1And2 ).toHaveBeenCalledTimes( 3 ); + expect( testInstance.findByType( 'div' ).props.data ).toEqual( { + count1: 0, + count2: 1, + } ); + + act( () => { + registry.dispatch( 'store-3' ).increment(); + } ); + + expect( selectCount1And2 ).toHaveBeenCalledTimes( 3 ); + expect( testInstance.findByType( 'div' ).props.data ).toEqual( { + count1: 0, + count2: 1, + } ); + } ); + + it( 're-calls the selector when deps changed', () => { + registry.registerStore( 'store-1', counterStore ); + registry.registerStore( 'store-2', counterStore ); + registry.registerStore( 'store-3', counterStore ); + + let renderer, dep, setDep; + const selectCount1AndDep = jest.fn(); + + const TestComponent = jest.fn( () => { + [ dep, setDep ] = useState( 0 ); + const state = useSelect( + ( select ) => + selectCount1AndDep() || { + count1: select( 'store-1' ).getCounter(), + dep, + }, + [ dep ] + ); + + return <div data={ state } />; + } ); + + act( () => { + renderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <TestComponent /> + </RegistryProvider> + ); + } ); + + const testInstance = renderer.root; + + expect( selectCount1AndDep ).toHaveBeenCalledTimes( 2 ); + expect( testInstance.findByType( 'div' ).props.data ).toEqual( { + count1: 0, + dep: 0, + } ); + + act( () => { + setDep( 1 ); + } ); + + expect( selectCount1AndDep ).toHaveBeenCalledTimes( 4 ); + expect( testInstance.findByType( 'div' ).props.data ).toEqual( { + count1: 0, + dep: 1, + } ); + + act( () => { + registry.dispatch( 'store-1' ).increment(); + } ); + + expect( selectCount1AndDep ).toHaveBeenCalledTimes( 5 ); + expect( testInstance.findByType( 'div' ).props.data ).toEqual( { + count1: 1, + dep: 1, + } ); + } ); + + it( 'handles registry selectors', () => { + const getCount1And2 = createRegistrySelector( + ( select ) => ( state ) => ( { + count1: state.counter, + count2: select( 'store-2' ).getCounter(), + } ) + ); + + registry.registerStore( 'store-1', { + ...counterStore, + selectors: { + ...counterStore.selectors, + getCount1And2, + }, + } ); + registry.registerStore( 'store-2', counterStore ); + + let renderer; + const selectCount1And2 = jest.fn(); + + const TestComponent = jest.fn( () => { + const state = useSelect( + ( select ) => + selectCount1And2() || + select( 'store-1' ).getCount1And2(), + [] + ); + + return <div data={ state } />; + } ); + + act( () => { + renderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <TestComponent /> + </RegistryProvider> + ); + } ); + + const testInstance = renderer.root; + + expect( selectCount1And2 ).toHaveBeenCalledTimes( 2 ); + expect( testInstance.findByType( 'div' ).props.data ).toEqual( { + count1: 0, + count2: 0, + } ); + + act( () => { + registry.dispatch( 'store-2' ).increment(); + } ); + + expect( selectCount1And2 ).toHaveBeenCalledTimes( 3 ); + expect( testInstance.findByType( 'div' ).props.data ).toEqual( { + count1: 0, + count2: 1, + } ); + } ); + + it( 'handles conditional statements in selectors', () => { + registry.registerStore( 'store-1', counterStore ); + registry.registerStore( 'store-2', counterStore ); + + let renderer, shouldSelectCount1, toggle; + const selectCount1 = jest.fn(); + const selectCount2 = jest.fn(); + + const TestComponent = jest.fn( () => { + [ shouldSelectCount1, toggle ] = useReducer( + ( should ) => ! should, + false + ); + const state = useSelect( + ( select ) => { + if ( shouldSelectCount1 ) { + selectCount1(); + select( 'store-1' ).getCounter(); + return 'count1'; + } + + selectCount2(); + select( 'store-2' ).getCounter(); + return 'count2'; + }, + [ shouldSelectCount1 ] + ); + + return <div data={ state } />; + } ); + + act( () => { + renderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <TestComponent /> + </RegistryProvider> + ); + } ); + + const testInstance = renderer.root; + + expect( selectCount1 ).toHaveBeenCalledTimes( 0 ); + expect( selectCount2 ).toHaveBeenCalledTimes( 2 ); + expect( testInstance.findByType( 'div' ).props.data ).toBe( + 'count2' + ); + + act( () => { + toggle(); + } ); + + expect( selectCount1 ).toHaveBeenCalledTimes( 2 ); + expect( selectCount2 ).toHaveBeenCalledTimes( 2 ); + expect( testInstance.findByType( 'div' ).props.data ).toBe( + 'count1' + ); + } ); + + it( "handles subscriptions to the parent's stores", () => { + registry.registerStore( 'parent-store', counterStore ); + + const subRegistry = createRegistry( {}, registry ); + subRegistry.registerStore( 'child-store', counterStore ); + + let renderer; + + const TestComponent = jest.fn( () => { + const state = useSelect( + ( select ) => ( { + parentCount: select( 'parent-store' ).getCounter(), + childCount: select( 'child-store' ).getCounter(), + } ), + [] + ); + + return <div data={ state } />; + } ); + + act( () => { + renderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <RegistryProvider value={ subRegistry }> + <TestComponent /> + </RegistryProvider> + </RegistryProvider> + ); + } ); + + const testInstance = renderer.root; + + expect( testInstance.findByType( 'div' ).props.data ).toEqual( { + parentCount: 0, + childCount: 0, + } ); + + act( () => { + registry.dispatch( 'parent-store' ).increment(); + } ); + + expect( testInstance.findByType( 'div' ).props.data ).toEqual( { + parentCount: 1, + childCount: 0, + } ); + } ); + + it( 'handles non-existing stores', () => { + registry.registerStore( 'store-1', counterStore ); + + let renderer; + + const TestComponent = jest.fn( () => { + const state = useSelect( + ( select ) => ( { + count1: select( 'store-1' ).getCounter(), + blank: select( 'non-existing-store' )?.getCounter(), + } ), + [] + ); + + return <div data={ state } />; + } ); + + act( () => { + renderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <TestComponent /> + </RegistryProvider> + ); + } ); + + const testInstance = renderer.root; + + expect( testInstance.findByType( 'div' ).props.data ).toEqual( { + count1: 0, + blank: undefined, + } ); + + act( () => { + registry.dispatch( 'store-1' ).increment(); + } ); + + expect( testInstance.findByType( 'div' ).props.data ).toEqual( { + count1: 1, + blank: undefined, + } ); + + // Test if the unsubscribers get called correctly. + renderer.unmount(); + } ); + + it( 'handles registration of a non-existing store during rendering', () => { + let renderer; + + const TestComponent = jest.fn( () => { + const state = useSelect( + ( select ) => + select( 'not-yet-registered-store' )?.getCounter(), + [] + ); + + return <div data={ state } />; + } ); + + act( () => { + renderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <TestComponent /> + </RegistryProvider> + ); + } ); + + const testInstance = renderer.root; + + expect( testInstance.findByType( 'div' ).props.data ).toBe( + undefined + ); + + act( () => { + registry.registerStore( + 'not-yet-registered-store', + counterStore + ); + } ); + + // This is not ideal, but is the way it's working before and we want to prevent breaking changes. + expect( testInstance.findByType( 'div' ).props.data ).toBe( + undefined + ); + + act( () => { + registry.dispatch( 'not-yet-registered-store' ).increment(); + } ); + + expect( testInstance.findByType( 'div' ).props.data ).toBe( 1 ); + + // Test if the unsubscribers get called correctly. + renderer.unmount(); + } ); + + it( 'handles registration of a non-existing store of sub-registry during rendering', () => { + let renderer; + + const subRegistry = createRegistry( {}, registry ); + + const TestComponent = jest.fn( () => { + const state = useSelect( + ( select ) => + select( + 'not-yet-registered-child-store' + )?.getCounter(), + [] + ); + + return <div data={ state } />; + } ); + + act( () => { + renderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <RegistryProvider value={ subRegistry }> + <TestComponent /> + </RegistryProvider> + </RegistryProvider> + ); + } ); + + const testInstance = renderer.root; + + expect( testInstance.findByType( 'div' ).props.data ).toBe( + undefined + ); + + act( () => { + registry.registerStore( + 'not-yet-registered-child-store', + counterStore + ); + } ); + + // This is not ideal, but is the way it's working before and we want to prevent breaking changes. + expect( testInstance.findByType( 'div' ).props.data ).toBe( + undefined + ); + + act( () => { + registry + .dispatch( 'not-yet-registered-child-store' ) + .increment(); + } ); + + expect( testInstance.findByType( 'div' ).props.data ).toBe( 1 ); + + // Test if the unsubscribers get called correctly. + renderer.unmount(); + } ); + + it( 'handles custom generic stores without a unsubscribe function', () => { + let renderer; + + function createCustomStore() { + let storeChanged = () => {}; + let counter = 0; + + const selectors = { + getCounter: () => counter, + }; + + const actions = { + increment: () => { + counter += 1; + storeChanged(); + }, + }; + + return { + getSelectors() { + return selectors; + }, + getActions() { + return actions; + }, + subscribe( listener ) { + storeChanged = listener; + }, + }; + } + + registry.registerGenericStore( + 'generic-store', + createCustomStore() + ); + + const TestComponent = jest.fn( () => { + const state = useSelect( + ( select ) => select( 'generic-store' ).getCounter(), + [] + ); + + return <div data={ state } />; + } ); + + act( () => { + renderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <TestComponent /> + </RegistryProvider> + ); + } ); + + const testInstance = renderer.root; + + expect( testInstance.findByType( 'div' ).props.data ).toBe( 0 ); + + act( () => { + registry.dispatch( 'generic-store' ).increment(); + } ); + + expect( testInstance.findByType( 'div' ).props.data ).toBe( 1 ); + + expect( () => renderer.unmount() ).not.toThrow(); + } ); + } ); } ); diff --git a/packages/data/src/components/with-dispatch/index.js b/packages/data/src/components/with-dispatch/index.js index d122530c307bd8..ede7d2d400e1dd 100644 --- a/packages/data/src/components/with-dispatch/index.js +++ b/packages/data/src/components/with-dispatch/index.js @@ -25,7 +25,7 @@ import { useDispatchWithMap } from '../use-dispatch'; * return <button type="button" onClick={ onClick }>{ children }</button>; * } * - * const { withDispatch } = wp.data; + * import { withDispatch } from '@wordpress/data'; * * const SaleButton = withDispatch( ( dispatch, ownProps ) => { * const { startSale } = dispatch( 'my-shop' ); @@ -62,7 +62,7 @@ import { useDispatchWithMap } from '../use-dispatch'; * return <button type="button" onClick={ onClick }>{ children }</button>; * } * - * const { withDispatch } = wp.data; + * import { withDispatch } from '@wordpress/data'; * * const SaleButton = withDispatch( ( dispatch, ownProps, { select } ) => { * // Stock number changes frequently. diff --git a/packages/data/src/components/with-select/index.js b/packages/data/src/components/with-select/index.js index 8c0801b0855b7d..60972896b10b65 100644 --- a/packages/data/src/components/with-select/index.js +++ b/packages/data/src/components/with-select/index.js @@ -18,6 +18,8 @@ import useSelect from '../use-select'; * * @example * ```js + * import { withSelect } from '@wordpress/data'; + * * function PriceDisplay( { price, currency } ) { * return new Intl.NumberFormat( 'en-US', { * style: 'currency', @@ -25,8 +27,6 @@ import useSelect from '../use-select'; * } ).format( price ); * } * - * const { withSelect } = wp.data; - * * const HammerPriceDisplay = withSelect( ( select, ownProps ) => { * const { getPrice } = select( 'my-shop' ); * const { currency } = ownProps; diff --git a/packages/data/src/components/with-select/test/index.js b/packages/data/src/components/with-select/test/index.js index 0eaf393a64b16f..efb094b0b3159c 100644 --- a/packages/data/src/components/with-select/test/index.js +++ b/packages/data/src/components/with-select/test/index.js @@ -34,7 +34,7 @@ describe( 'withSelect', () => { // In normal circumstances, the fact that we have to add an arbitrary // prefix to the variable name would be concerning, and perhaps an // argument that we ought to expect developer to use select from the - // wp.data export. But in-fact, this serves as a good deterrent for + // `@wordpress/data` export. But in-fact, this serves as a good deterrent for // including both `withSelect` and `select` in the same scope, which // shouldn't occur for a typical component, and if it did might wrongly // encourage the developer to use `select` within the component itself. @@ -666,11 +666,7 @@ describe( 'withSelect', () => { registry.dispatch( 'childRender' ).toggleRender(); } ); - // 3 times because - // - 1 on initial render - // - 1 on effect before subscription set. - // - 1 child subscription fires. - expect( childMapSelectToProps ).toHaveBeenCalledTimes( 3 ); + expect( childMapSelectToProps ).toHaveBeenCalledTimes( 2 ); expect( parentMapSelectToProps ).toHaveBeenCalledTimes( 4 ); expect( ChildOriginalComponent ).toHaveBeenCalledTimes( 1 ); expect( ParentOriginalComponent ).toHaveBeenCalledTimes( 2 ); diff --git a/packages/data/src/index.js b/packages/data/src/index.js index 911b6e02983798..457868239baae3 100644 --- a/packages/data/src/index.js +++ b/packages/data/src/index.js @@ -9,6 +9,8 @@ import combineReducers from 'turbo-combine-reducers'; import defaultRegistry from './default-registry'; import * as plugins from './plugins'; +/** @typedef {import('./types').WPDataStore} WPDataStore */ + export { default as withSelect } from './components/with-select'; export { default as withDispatch } from './components/with-dispatch'; export { default as withRegistry } from './components/with-registry'; @@ -23,6 +25,7 @@ export { AsyncModeProvider } from './components/async-mode-provider'; export { createRegistry } from './registry'; export { createRegistrySelector, createRegistryControl } from './factory'; export { controls } from './controls'; +export { default as createReduxStore } from './redux-store'; /** * Object of available plugins to use with a registry. @@ -42,7 +45,7 @@ export { plugins }; * * @example * ```js - * const { combineReducers, registerStore } = wp.data; + * import { combineReducers, createReduxStore, register } from '@wordpress/data'; * * const prices = ( state = {}, action ) => { * return action.type === 'SET_PRICE' ? @@ -59,12 +62,13 @@ export { plugins }; * state; * }; * - * registerStore( 'my-shop', { + * const store = createReduxStore( 'my-shop', { * reducer: combineReducers( { * prices, * discountPercent, * } ), * } ); + * register( store ); * ``` * * @return {Function} A reducer that invokes every reducer inside the reducers @@ -73,15 +77,16 @@ export { plugins }; export { combineReducers }; /** - * Given the name of a registered store, returns an object of the store's selectors. + * Given the name or definition of a registered store, returns an object of the store's selectors. * The selector functions are been pre-bound to pass the current state automatically. * As a consumer, you need only pass arguments of the selector, if applicable. * - * @param {string} name Store name. + * @param {string|WPDataStore} storeNameOrDefinition Unique namespace identifier for the store + * or the store definition. * * @example * ```js - * const { select } = wp.data; + * import { select } from '@wordpress/data'; * * select( 'my-shop' ).getPrice( 'hammer' ); * ``` @@ -96,11 +101,12 @@ export const select = defaultRegistry.select; * and modified so that they return promises that resolve to their eventual values, * after any resolvers have ran. * - * @param {string} name Store name. + * @param {string|WPDataStore} storeNameOrDefinition Unique namespace identifier for the store + * or the store definition. * * @example * ```js - * const { __experimentalResolveSelect } = wp.data; + * import { __experimentalResolveSelect } from '@wordpress/data'; * * __experimentalResolveSelect( 'my-shop' ).getPrice( 'hammer' ).then(console.log) * ``` @@ -117,11 +123,12 @@ export const __experimentalResolveSelect = * Note: Action creators returned by the dispatch will return a promise when * they are called. * - * @param {string} name Store name. + * @param {string|WPDataStore} storeNameOrDefinition Unique namespace identifier for the store + * or the store definition. * * @example * ```js - * const { dispatch } = wp.data; + * import { dispatch } from '@wordpress/data'; * * dispatch( 'my-shop' ).setPrice( 'hammer', 9.75 ); * ``` @@ -138,7 +145,7 @@ export const dispatch = defaultRegistry.dispatch; * * @example * ```js - * const { subscribe } = wp.data; + * import { subscribe } from '@wordpress/data'; * * const unsubscribe = subscribe( () => { * // You could use this opportunity to test whether the derived result of a @@ -154,6 +161,8 @@ export const subscribe = defaultRegistry.subscribe; /** * Registers a generic store. * + * @deprecated Use `register` instead. + * * @param {string} key Store registry key. * @param {Object} config Configuration (getSelectors, getActions, subscribe). */ @@ -162,8 +171,10 @@ export const registerGenericStore = defaultRegistry.registerGenericStore; /** * Registers a standard `@wordpress/data` store. * - * @param {string} reducerKey Reducer key. - * @param {Object} options Store description (reducer, actions, selectors, resolvers). + * @deprecated Use `register` instead. + * + * @param {string} storeName Unique namespace identifier for the store. + * @param {Object} options Store description (reducer, actions, selectors, resolvers). * * @return {Object} Registered store object. */ @@ -177,3 +188,23 @@ export const registerStore = defaultRegistry.registerStore; * @param {Object} plugin Plugin object. */ export const use = defaultRegistry.use; + +/** + * Registers a standard `@wordpress/data` store definition. + * + * @example + * ```js + * import { createReduxStore, register } from '@wordpress/data'; + * + * const store = createReduxStore( 'demo', { + * reducer: ( state = 'OK' ) => state, + * selectors: { + * getValue: ( state ) => state, + * }, + * } ); + * register( store ); + * ``` + * + * @param {WPDataStore} store Store definition. + */ +export const register = defaultRegistry.register; diff --git a/packages/data/src/plugins/persistence/index.js b/packages/data/src/plugins/persistence/index.js index fc9ae014d4628f..cfebe55cac98d8 100644 --- a/packages/data/src/plugins/persistence/index.js +++ b/packages/data/src/plugins/persistence/index.js @@ -126,15 +126,15 @@ function persistencePlugin( registry, pluginOptions ) { /** * Creates an enhanced store dispatch function, triggering the state of the - * given reducer key to be persisted when changed. + * given store name to be persisted when changed. * - * @param {Function} getState Function which returns current state. - * @param {string} reducerKey Reducer key. - * @param {?Array<string>} keys Optional subset of keys to save. + * @param {Function} getState Function which returns current state. + * @param {string} storeName Store name. + * @param {?Array<string>} keys Optional subset of keys to save. * * @return {Function} Enhanced dispatch function. */ - function createPersistOnChange( getState, reducerKey, keys ) { + function createPersistOnChange( getState, storeName, keys ) { let getPersistedState; if ( Array.isArray( keys ) ) { // Given keys, the persisted state should by produced as an object @@ -166,20 +166,20 @@ function persistencePlugin( registry, pluginOptions ) { nextState: getState(), } ); if ( state !== lastState ) { - persistence.set( reducerKey, state ); + persistence.set( storeName, state ); lastState = state; } }; } return { - registerStore( reducerKey, options ) { + registerStore( storeName, options ) { if ( ! options.persist ) { - return registry.registerStore( reducerKey, options ); + return registry.registerStore( storeName, options ); } // Load from persistence to use as initial state. - const persistedState = persistence.get()[ reducerKey ]; + const persistedState = persistence.get()[ storeName ]; if ( persistedState !== undefined ) { let initialState = options.reducer( options.initialState, { type: '@@WP/PERSISTENCE_RESTORE', @@ -207,12 +207,12 @@ function persistencePlugin( registry, pluginOptions ) { }; } - const store = registry.registerStore( reducerKey, options ); + const store = registry.registerStore( storeName, options ); store.subscribe( createPersistOnChange( store.getState, - reducerKey, + storeName, options.persist ) ); diff --git a/packages/data/src/namespace-store/index.js b/packages/data/src/redux-store/index.js similarity index 69% rename from packages/data/src/namespace-store/index.js rename to packages/data/src/redux-store/index.js index 8ce882f0e6be8b..edfd3083712ccb 100644 --- a/packages/data/src/namespace-store/index.js +++ b/packages/data/src/redux-store/index.js @@ -21,6 +21,10 @@ import metadataReducer from './metadata/reducer'; import * as metadataSelectors from './metadata/selectors'; import * as metadataActions from './metadata/actions'; +/** @typedef {import('../types').WPDataRegistry} WPDataRegistry */ +/** @typedef {import('../types').WPDataStore} WPDataStore */ +/** @typedef {import('../types').WPDataReduxStoreConfig} WPDataReduxStoreConfig */ + /** * Create a cache to track whether resolvers started running or not. * @@ -50,98 +54,112 @@ function createResolversCache() { } /** - * @typedef {WPDataRegistry} WPDataRegistry - */ - -/** - * Creates a namespace object with a store derived from the reducer given. + * Creates a data store definition for the provided Redux store options containing + * properties describing reducer, actions, selectors, controls and resolvers. * - * @param {string} key Unique namespace identifier. - * @param {Object} options Registered store options, with properties - * describing reducer, actions, selectors, and - * resolvers. - * @param {WPDataRegistry} registry Registry reference. + * @example + * ```js + * import { createReduxStore } from '@wordpress/data'; + * + * const store = createReduxStore( 'demo', { + * reducer: ( state = 'OK' ) => state, + * selectors: { + * getValue: ( state ) => state, + * }, + * } ); + * ``` + * + * @param {string} key Unique namespace identifier. + * @param {WPDataReduxStoreConfig} options Registered store options, with properties + * describing reducer, actions, selectors, + * and resolvers. * - * @return {Object} Store Object. + * @return {WPDataStore} Store Object. */ -export default function createNamespace( key, options, registry ) { - const reducer = options.reducer; - const store = createReduxStore( key, options, registry ); - const resolversCache = createResolversCache(); - - let resolvers; - const actions = mapActions( - { - ...metadataActions, - ...options.actions, - }, - store - ); - let selectors = mapSelectors( - { - ...mapValues( - metadataSelectors, - ( selector ) => ( state, ...args ) => - selector( state.metadata, ...args ) - ), - ...mapValues( options.selectors, ( selector ) => { - if ( selector.isRegistrySelector ) { - selector.registry = registry; - } - - return ( state, ...args ) => selector( state.root, ...args ); - } ), - }, - store - ); - if ( options.resolvers ) { - const result = mapResolvers( - options.resolvers, - selectors, - store, - resolversCache - ); - resolvers = result.resolvers; - selectors = result.selectors; - } +export default function createReduxStore( key, options ) { + return { + name: key, + instantiate: ( registry ) => { + const reducer = options.reducer; + const store = instantiateReduxStore( key, options, registry ); + const resolversCache = createResolversCache(); + + let resolvers; + const actions = mapActions( + { + ...metadataActions, + ...options.actions, + }, + store + ); + let selectors = mapSelectors( + { + ...mapValues( + metadataSelectors, + ( selector ) => ( state, ...args ) => + selector( state.metadata, ...args ) + ), + ...mapValues( options.selectors, ( selector ) => { + if ( selector.isRegistrySelector ) { + selector.registry = registry; + } + + return ( state, ...args ) => + selector( state.root, ...args ); + } ), + }, + store + ); + if ( options.resolvers ) { + const result = mapResolvers( + options.resolvers, + selectors, + store, + resolversCache + ); + resolvers = result.resolvers; + selectors = result.selectors; + } - const getSelectors = () => selectors; - const getActions = () => actions; - - // We have some modules monkey-patching the store object - // It's wrong to do so but until we refactor all of our effects to controls - // We need to keep the same "store" instance here. - store.__unstableOriginalGetState = store.getState; - store.getState = () => store.__unstableOriginalGetState().root; - - // Customize subscribe behavior to call listeners only on effective change, - // not on every dispatch. - const subscribe = - store && - ( ( listener ) => { - let lastState = store.__unstableOriginalGetState(); - store.subscribe( () => { - const state = store.__unstableOriginalGetState(); - const hasChanged = state !== lastState; - lastState = state; - - if ( hasChanged ) { - listener(); - } - } ); - } ); + const getSelectors = () => selectors; + const getActions = () => actions; + + // We have some modules monkey-patching the store object + // It's wrong to do so but until we refactor all of our effects to controls + // We need to keep the same "store" instance here. + store.__unstableOriginalGetState = store.getState; + store.getState = () => store.__unstableOriginalGetState().root; + + // Customize subscribe behavior to call listeners only on effective change, + // not on every dispatch. + const subscribe = + store && + ( ( listener ) => { + let lastState = store.__unstableOriginalGetState(); + return store.subscribe( () => { + const state = store.__unstableOriginalGetState(); + const hasChanged = state !== lastState; + lastState = state; + + if ( hasChanged ) { + listener(); + } + } ); + } ); - // This can be simplified to just { subscribe, getSelectors, getActions } - // Once we remove the use function. - return { - reducer, - store, - actions, - selectors, - resolvers, - getSelectors, - getActions, - subscribe, + // This can be simplified to just { subscribe, getSelectors, getActions } + // Once we remove the use function. + return { + reducer, + store, + actions, + selectors, + resolvers, + getSelectors, + getActions, + subscribe, + }; + }, }; } @@ -150,13 +168,13 @@ export default function createNamespace( key, options, registry ) { * * @param {string} key Unique namespace identifier. * @param {Object} options Registered store options, with properties - * describing reducer, actions, selectors, and - * resolvers. + * describing reducer, actions, selectors, + * and resolvers. * @param {WPDataRegistry} registry Registry reference. * * @return {Object} Newly created redux store. */ -function createReduxStore( key, options, registry ) { +function instantiateReduxStore( key, options, registry ) { const controls = { ...options.controls, ...builtinControls, diff --git a/packages/data/src/namespace-store/metadata/actions.js b/packages/data/src/redux-store/metadata/actions.js similarity index 100% rename from packages/data/src/namespace-store/metadata/actions.js rename to packages/data/src/redux-store/metadata/actions.js diff --git a/packages/data/src/namespace-store/metadata/reducer.js b/packages/data/src/redux-store/metadata/reducer.js similarity index 100% rename from packages/data/src/namespace-store/metadata/reducer.js rename to packages/data/src/redux-store/metadata/reducer.js diff --git a/packages/data/src/namespace-store/metadata/selectors.js b/packages/data/src/redux-store/metadata/selectors.js similarity index 100% rename from packages/data/src/namespace-store/metadata/selectors.js rename to packages/data/src/redux-store/metadata/selectors.js diff --git a/packages/data/src/namespace-store/metadata/test/reducer.js b/packages/data/src/redux-store/metadata/test/reducer.js similarity index 100% rename from packages/data/src/namespace-store/metadata/test/reducer.js rename to packages/data/src/redux-store/metadata/test/reducer.js diff --git a/packages/data/src/namespace-store/metadata/test/selectors.js b/packages/data/src/redux-store/metadata/test/selectors.js similarity index 100% rename from packages/data/src/namespace-store/metadata/test/selectors.js rename to packages/data/src/redux-store/metadata/test/selectors.js diff --git a/packages/data/src/namespace-store/metadata/test/utils.js b/packages/data/src/redux-store/metadata/test/utils.js similarity index 100% rename from packages/data/src/namespace-store/metadata/test/utils.js rename to packages/data/src/redux-store/metadata/test/utils.js diff --git a/packages/data/src/namespace-store/metadata/utils.js b/packages/data/src/redux-store/metadata/utils.js similarity index 100% rename from packages/data/src/namespace-store/metadata/utils.js rename to packages/data/src/redux-store/metadata/utils.js diff --git a/packages/data/src/namespace-store/test/index.js b/packages/data/src/redux-store/test/index.js similarity index 100% rename from packages/data/src/namespace-store/test/index.js rename to packages/data/src/redux-store/test/index.js diff --git a/packages/data/src/registry.js b/packages/data/src/registry.js index fe3eb80d7efd93..6a0ca9fc4a7a4d 100644 --- a/packages/data/src/registry.js +++ b/packages/data/src/registry.js @@ -1,15 +1,17 @@ /** * External dependencies */ -import { omit, without, mapValues } from 'lodash'; +import { omit, without, mapValues, isObject } from 'lodash'; import memize from 'memize'; /** * Internal dependencies */ -import createNamespace from './namespace-store'; +import createReduxStore from './redux-store'; import createCoreDataStore from './store'; +/** @typedef {import('./types').WPDataStore} WPDataStore */ + /** * @typedef {Object} WPDataRegistry An isolated orchestrator of store registrations. * @@ -48,6 +50,7 @@ import createCoreDataStore from './store'; export function createRegistry( storeConfigs = {}, parent = null ) { const stores = {}; let listeners = []; + const __experimentalListeningStores = new Set(); /** * Global listener called for each store's update. @@ -74,18 +77,29 @@ export function createRegistry( storeConfigs = {}, parent = null ) { /** * Calls a selector given the current state and extra arguments. * - * @param {string} reducerKey Part of the state shape to register the - * selectors for. + * @param {string|WPDataStore} storeNameOrDefinition Unique namespace identifier for the store + * or the store definition. * * @return {*} The selector's returned value. */ - function select( reducerKey ) { - const store = stores[ reducerKey ]; + function select( storeNameOrDefinition ) { + const storeName = isObject( storeNameOrDefinition ) + ? storeNameOrDefinition.name + : storeNameOrDefinition; + __experimentalListeningStores.add( storeName ); + const store = stores[ storeName ]; if ( store ) { return store.getSelectors(); } - return parent && parent.select( reducerKey ); + return parent && parent.select( storeName ); + } + + function __experimentalMarkListeningStores( callback, ref ) { + __experimentalListeningStores.clear(); + const result = callback.call( this ); + ref.current = Array.from( __experimentalListeningStores ); + return result; } const getResolveSelectors = memize( @@ -135,30 +149,33 @@ export function createRegistry( storeConfigs = {}, parent = null ) { * and modified so that they return promises that resolve to their eventual values, * after any resolvers have ran. * - * @param {string} reducerKey Part of the state shape to register the - * selectors for. + * @param {string|WPDataStore} storeNameOrDefinition Unique namespace identifier for the store + * or the store definition. * * @return {Object} Each key of the object matches the name of a selector. */ - function __experimentalResolveSelect( reducerKey ) { - return getResolveSelectors( select( reducerKey ) ); + function __experimentalResolveSelect( storeNameOrDefinition ) { + return getResolveSelectors( select( storeNameOrDefinition ) ); } /** * Returns the available actions for a part of the state. * - * @param {string} reducerKey Part of the state shape to dispatch the - * action for. + * @param {string|WPDataStore} storeNameOrDefinition Unique namespace identifier for the store + * or the store definition. * * @return {*} The action's returned value. */ - function dispatch( reducerKey ) { - const store = stores[ reducerKey ]; + function dispatch( storeNameOrDefinition ) { + const storeName = isObject( storeNameOrDefinition ) + ? storeNameOrDefinition.name + : storeNameOrDefinition; + const store = stores[ storeName ]; if ( store ) { return store.getActions(); } - return parent && parent.dispatch( reducerKey ); + return parent && parent.dispatch( storeName ); } // @@ -196,6 +213,38 @@ export function createRegistry( storeConfigs = {}, parent = null ) { config.subscribe( globalListener ); } + /** + * Registers a new store definition. + * + * @param {WPDataStore} store Store definition. + */ + function register( store ) { + registerGenericStore( store.name, store.instantiate( registry ) ); + } + + /** + * Subscribe handler to a store. + * + * @param {string[]} storeName The store name. + * @param {Function} handler The function subscribed to the store. + * @return {Function} A function to unsubscribe the handler. + */ + function __experimentalSubscribeStore( storeName, handler ) { + if ( storeName in stores ) { + return stores[ storeName ].subscribe( handler ); + } + + // Trying to access a store that hasn't been registered, + // this is a pattern rarely used but seen in some places. + // We fallback to regular `subscribe` here for backward-compatibility for now. + // See https://github.com/WordPress/gutenberg/pull/27466 for more info. + if ( ! parent ) { + return subscribe( handler ); + } + + return parent.__experimentalSubscribeStore( storeName, handler ); + } + let registry = { registerGenericStore, stores, @@ -205,24 +254,29 @@ export function createRegistry( storeConfigs = {}, parent = null ) { __experimentalResolveSelect, dispatch, use, + register, + __experimentalMarkListeningStores, + __experimentalSubscribeStore, }; /** * Registers a standard `@wordpress/data` store. * - * @param {string} reducerKey Reducer key. + * @param {string} storeName Unique namespace identifier. * @param {Object} options Store description (reducer, actions, selectors, resolvers). * * @return {Object} Registered store object. */ - registry.registerStore = ( reducerKey, options ) => { + registry.registerStore = ( storeName, options ) => { if ( ! options.reducer ) { throw new TypeError( 'Must specify store reducer' ); } - const namespace = createNamespace( reducerKey, options, registry ); - registerGenericStore( reducerKey, namespace ); - return namespace.store; + const store = createReduxStore( storeName, options ).instantiate( + registry + ); + registerGenericStore( storeName, store ); + return store.store; }; // diff --git a/packages/data/src/store/index.js b/packages/data/src/store/index.js index 8310610cb59364..fa153fd9b640b5 100644 --- a/packages/data/src/store/index.js +++ b/packages/data/src/store/index.js @@ -1,10 +1,10 @@ function createCoreDataStore( registry ) { - const getCoreDataSelector = ( selectorName ) => ( reducerKey, ...args ) => { - return registry.select( reducerKey )[ selectorName ]( ...args ); + const getCoreDataSelector = ( selectorName ) => ( key, ...args ) => { + return registry.select( key )[ selectorName ]( ...args ); }; - const getCoreDataAction = ( actionName ) => ( reducerKey, ...args ) => { - return registry.dispatch( reducerKey )[ actionName ]( ...args ); + const getCoreDataAction = ( actionName ) => ( key, ...args ) => { + return registry.dispatch( key )[ actionName ]( ...args ); }; return { diff --git a/packages/data/src/test/registry.js b/packages/data/src/test/registry.js index ad4b47d308144c..c36bbe98654188 100644 --- a/packages/data/src/test/registry.js +++ b/packages/data/src/test/registry.js @@ -8,6 +8,7 @@ import { castArray, mapValues } from 'lodash'; */ import { createRegistry } from '../registry'; import { createRegistrySelector } from '../factory'; +import createReduxStore from '../redux-store'; jest.useFakeTimers(); @@ -505,6 +506,44 @@ describe( 'createRegistry', () => { } ); } ); + describe( 'register', () => { + it( 'should work with the store definition as param for select', () => { + const store = createReduxStore( 'demo', { + reducer: ( state = 'OK' ) => state, + selectors: { + getValue: ( state ) => state, + }, + } ); + registry.register( store ); + + expect( registry.select( store ).getValue() ).toBe( 'OK' ); + } ); + + it( 'should work with the store definition as param for dispatch', async () => { + const store = createReduxStore( 'demo', { + reducer( state = 'OK', action ) { + if ( action.type === 'UPDATE' ) { + return 'UPDATED'; + } + return state; + }, + actions: { + update() { + return { type: 'UPDATE' }; + }, + }, + selectors: { + getValue: ( state ) => state, + }, + } ); + registry.register( store ); + + expect( registry.select( store ).getValue() ).toBe( 'OK' ); + await registry.dispatch( store ).update(); + expect( registry.select( store ).getValue() ).toBe( 'UPDATED' ); + } ); + } ); + describe( 'select', () => { it( 'registers multiple selectors to the public API', () => { const selector1 = jest.fn( () => 'result1' ); diff --git a/packages/data/src/types.d.ts b/packages/data/src/types.d.ts new file mode 100644 index 00000000000000..d3456f6fb79bd7 --- /dev/null +++ b/packages/data/src/types.d.ts @@ -0,0 +1,32 @@ +export type WPDataFunctionOrGeneratorArray = Array< Function | Generator >; +export type WPDataFunctionArray = Array< Function >; + +export interface WPDataAttachedStore { + getSelectors: () => WPDataFunctionArray; + getActions: () => WPDataFunctionArray; + subscribe: ( listener: () => void ) => () => void; +} + +export interface WPDataStore { + /** + * Store Name + */ + name: string; + + /** + * Store configuration object. + */ + instantiate: ( registry: WPDataRegistry ) => WPDataAttachedStore; +} + +export interface WPDataReduxStoreConfig { + reducer: ( state: any, action: any ) => any; + actions?: WPDataFunctionOrGeneratorArray; + resolvers?: WPDataFunctionOrGeneratorArray; + selectors?: WPDataFunctionArray; + controls?: WPDataFunctionArray; +} + +export interface WPDataRegistry { + register: ( store: WPDataStore ) => void; +} diff --git a/packages/date/package.json b/packages/date/package.json index 5f64325f5fde98..7019a1eaf7fb93 100644 --- a/packages/date/package.json +++ b/packages/date/package.json @@ -22,7 +22,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "moment": "^2.22.1", "moment-timezone": "^0.5.31" }, diff --git a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md index 05c96b1fe2309d..6278dd0c9367b2 100644 --- a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md +++ b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### New feature + +- Make the plugin compatible with webpack 5. + ## 2.7.0 (2020-06-15) ### New feature diff --git a/packages/dependency-extraction-webpack-plugin/lib/index.js b/packages/dependency-extraction-webpack-plugin/lib/index.js index bd4c0ad992fc93..0cc84b99a23846 100644 --- a/packages/dependency-extraction-webpack-plugin/lib/index.js +++ b/packages/dependency-extraction-webpack-plugin/lib/index.js @@ -3,10 +3,9 @@ */ const { createHash } = require( 'crypto' ); const path = require( 'path' ); -const { ExternalsPlugin } = require( 'webpack' ); -const { RawSource } = require( 'webpack-sources' ); -// Ignore reason: json2php is untyped -// @ts-ignore +const webpack = require( 'webpack' ); +// In webpack 5 there is a `webpack.sources` field but for webpack 4 we have to fallback to the `webpack-sources` package. +const { RawSource } = webpack.sources || require( 'webpack-sources' ); const json2php = require( 'json2php' ); /** @@ -17,53 +16,8 @@ const { defaultRequestToHandle, } = require( './util' ); -/** - * Map module request to an external. - * - * @callback RequestToExternal - * - * @param {string} request Module request. - * - * @return {string|string[]|void} Return `undefined` to ignore the request. - * Return `string|string[]` to map the request to an external. - */ - -/** - * Map module request to a script handle. - * - * @callback RequestToHandle - * - * @param {string} request Module request. - * - * @return {string|void} Return `undefined` to use the same name as the module. - * Return `string` to map the request to a specific script handle. - */ - -/** - * @typedef AssetData - * - * @property {string} version String representing a particular build - * @property {string[]} dependencies The script dependencies - */ - -/** - * @typedef Options - * - * @property {boolean} injectPolyfill Force wp-polyfill to be included in each entry point's dependency list. This is like importing `@wordpress/polyfill` for each entry point. - * @property {boolean} useDefaults Set to `false` to disable the default WordPress script request handling. - * @property {'php'|'json'} outputFormat The output format for the generated asset file. - * @property {RequestToExternal|undefined} [requestToExternal] Map module requests to an external. - * @property {RequestToHandle|undefined} [requestToHandle] Map module requests to a script handle. - * @property {string|null} combinedOutputFile This option is useful only when the combineAssets option is enabled. It allows providing a custom output file for the generated single assets file. It's possible to provide a path that is relative to the output directory. - * @property {boolean|undefined} combineAssets By default, one asset file is created for each entry point. When this flag is set to true, all information about assets is combined into a single assets.(json|php) file generated in the output directory. - */ - class DependencyExtractionWebpackPlugin { - /** - * @param {Partial<Options>} options - */ constructor( options ) { - /** @type {Options} */ this.options = Object.assign( { combineAssets: false, @@ -75,32 +29,23 @@ class DependencyExtractionWebpackPlugin { options ); - /** + /* * Track requests that are externalized. * * Because we don't have a closed set of dependencies, we need to track what has * been externalized so we can recognize them in a later phase when the dependency * lists are generated. - * - * @type {Set<string>} */ this.externalizedDeps = new Set(); // Offload externalization work to the ExternalsPlugin. - this.externalsPlugin = new ExternalsPlugin( - 'this', + this.externalsPlugin = new webpack.ExternalsPlugin( + 'window', this.externalizeWpDeps.bind( this ) ); } - /* eslint-disable jsdoc/valid-types */ - /** - * @param {Parameters<WebpackExternalsFunction>[0]} _context - * @param {Parameters<WebpackExternalsFunction>[1]} request - * @param {Parameters<WebpackExternalsFunction>[2]} callback - */ externalizeWpDeps( _context, request, callback ) { - /* eslint-enable jsdoc/valid-types */ let externalRequest; // Handle via options.requestToExternal first @@ -119,16 +64,12 @@ class DependencyExtractionWebpackPlugin { if ( externalRequest ) { this.externalizedDeps.add( request ); - return callback( null, { this: externalRequest } ); + return callback( null, externalRequest ); } return callback(); } - /** - * @param {string} request - * @return {string} Transformed request - */ mapRequestToDependency( request ) { // Handle via options.requestToHandle first if ( typeof this.options.requestToHandle === 'function' ) { @@ -150,10 +91,6 @@ class DependencyExtractionWebpackPlugin { return request; } - /** - * @param {Object} asset - * @return {string} Stringified asset - */ stringify( asset ) { if ( this.options.outputFormat === 'php' ) { return `<?php return ${ json2php( @@ -164,20 +101,9 @@ class DependencyExtractionWebpackPlugin { return JSON.stringify( asset ); } - /** - * @param {WebpackCompiler} compiler - * @return {void} - */ apply( compiler ) { this.externalsPlugin.apply( compiler ); - // Assert the `string` type for output filename. - // The type indicates the option may be `undefined`. - // However, at this point in compilation, webpack has filled the options in if - // they were not provided. - const outputFilename = /** @type {{filename:string}} */ ( compiler - .options.output ).filename; - compiler.hooks.emit.tap( this.constructor.name, ( compilation ) => { const { combineAssets, @@ -186,7 +112,6 @@ class DependencyExtractionWebpackPlugin { outputFormat, } = this.options; - /** @type {Record<string, AssetData>} */ const combinedAssetsData = {}; // Process each entry point independently. @@ -194,29 +119,35 @@ class DependencyExtractionWebpackPlugin { entrypointName, entrypoint, ] of compilation.entrypoints.entries() ) { - /** @type {Set<string>} */ const entrypointExternalizedWpDeps = new Set(); if ( injectPolyfill ) { entrypointExternalizedWpDeps.add( 'wp-polyfill' ); } + const processModule = ( { userRequest } ) => { + if ( this.externalizedDeps.has( userRequest ) ) { + const scriptDependency = this.mapRequestToDependency( + userRequest + ); + entrypointExternalizedWpDeps.add( scriptDependency ); + } + }; + // Search for externalized modules in all chunks. for ( const chunk of entrypoint.chunks ) { - for ( const { userRequest } of chunk.modulesIterable ) { - if ( this.externalizedDeps.has( userRequest ) ) { - const scriptDependency = this.mapRequestToDependency( - userRequest - ); - entrypointExternalizedWpDeps.add( - scriptDependency - ); + for ( const chunkModule of chunk.modulesIterable ) { + processModule( chunkModule ); + // loop through submodules of ConcatenatedModule + if ( chunkModule.modules ) { + for ( const concatModule of chunkModule.modules ) { + processModule( concatModule ); + } } } } const runtimeChunk = entrypoint.getRuntimeChunk(); - /** @type {AssetData} */ const assetData = { // Get a sorted array so we can produce a stable, stringified representation. dependencies: Array.from( @@ -229,15 +160,18 @@ class DependencyExtractionWebpackPlugin { // Determine a filename for the asset file. const [ filename, query ] = entrypointName.split( '?', 2 ); - const buildFilename = compilation.getPath( outputFilename, { - chunk: runtimeChunk, - filename, - query, - basename: basename( filename ), - contentHash: createHash( 'md4' ) - .update( assetString ) - .digest( 'hex' ), - } ); + const buildFilename = compilation.getPath( + compiler.options.output.filename, + { + chunk: runtimeChunk, + filename, + query, + basename: basename( filename ), + contentHash: createHash( 'md4' ) + .update( assetString ) + .digest( 'hex' ), + } + ); if ( combineAssets ) { combinedAssetsData[ buildFilename ] = assetData; @@ -283,10 +217,6 @@ class DependencyExtractionWebpackPlugin { } } -/** - * @param {string} name - * @return {string} Basename - */ function basename( name ) { if ( ! name.includes( '/' ) ) { return name; @@ -295,8 +225,3 @@ function basename( name ) { } module.exports = DependencyExtractionWebpackPlugin; - -/** - * @typedef {import('webpack').Compiler} WebpackCompiler - * @typedef {import('webpack').ExternalsFunctionElement} WebpackExternalsFunction - */ diff --git a/packages/dependency-extraction-webpack-plugin/lib/types.d.ts b/packages/dependency-extraction-webpack-plugin/lib/types.d.ts new file mode 100644 index 00000000000000..d0c9f049ec6748 --- /dev/null +++ b/packages/dependency-extraction-webpack-plugin/lib/types.d.ts @@ -0,0 +1,18 @@ +import { Compiler } from 'webpack'; + +export = DependencyExtractionWebpackPlugin; + +declare class DependencyExtractionWebpackPlugin { + constructor(options: DependencyExtractionWebpackPluginOptions); + apply(compiler: Compiler): void; +} + +declare interface DependencyExtractionWebpackPluginOptions { + injectPolyfill?: boolean; + useDefaults?: boolean; + outputFormat?: 'php' | 'json'; + requestToExternal?: (request: string) => string | string[] | undefined; + requestToHandle?: (request: string) => string | undefined; + combinedOutputFile?: string | null; + combineAssets?: boolean; +} diff --git a/packages/dependency-extraction-webpack-plugin/lib/util.js b/packages/dependency-extraction-webpack-plugin/lib/util.js index 21854bb50c0066..a1817f65c7781c 100644 --- a/packages/dependency-extraction-webpack-plugin/lib/util.js +++ b/packages/dependency-extraction-webpack-plugin/lib/util.js @@ -5,11 +5,12 @@ const BUNDLED_PACKAGES = [ '@wordpress/icons', '@wordpress/interface' ]; * Default request to global transformation * * Transform @wordpress dependencies: + * - request `@wordpress/api-fetch` becomes `[ 'wp', 'apiFetch' ]` + * - request `@wordpress/i18n` becomes `[ 'wp', 'i18n' ]` * - * request `@wordpress/api-fetch` becomes `wp.apiFetch` - * request `@wordpress/i18n` becomes `wp.i18n` - * - * @type {import('.').RequestToExternal} + * @param {string} request Module request (the module name in `import from`) to be transformed + * @return {string|string[]|undefined} The resulting external definition. Return `undefined` + * to ignore the request. Return `string|string[]` to map the request to an external. */ function defaultRequestToExternal( request ) { switch ( request ) { @@ -49,11 +50,12 @@ function defaultRequestToExternal( request ) { * Default request to WordPress script handle transformation * * Transform @wordpress dependencies: + * - request `@wordpress/i18n` becomes `wp-i18n` + * - request `@wordpress/escape-html` becomes `wp-escape-html` * - * request `@wordpress/i18n` becomes `wp-i18n` - * request `@wordpress/escape-html` becomes `wp-escape-html` - * - * @type {import('.').RequestToHandle} + * @param {string} request Module request (the module name in `import from`) to be transformed + * @return {string|undefined} WordPress script handle to map the request to. Return `undefined` + * to use the same name as the module. */ function defaultRequestToHandle( request ) { switch ( request ) { @@ -76,7 +78,6 @@ function defaultRequestToHandle( request ) { * following numbers. * * @param {string} string Input dash-delimited string. - * * @return {string} Camel-cased string. */ function camelCaseDash( string ) { diff --git a/packages/dependency-extraction-webpack-plugin/package.json b/packages/dependency-extraction-webpack-plugin/package.json index c11ba4edd1aa99..ef0348a8c99cd3 100644 --- a/packages/dependency-extraction-webpack-plugin/package.json +++ b/packages/dependency-extraction-webpack-plugin/package.json @@ -24,12 +24,14 @@ "build-types" ], "main": "lib/index.js", - "types": "build-types", + "types": "lib/types.d.ts", "dependencies": { "json2php": "^0.0.4", - "webpack": "^4.8.3", "webpack-sources": "^1.3.0" }, + "peerDependencies": { + "webpack": "^4.8.3 || ^5.0.0" + }, "publishConfig": { "access": "public" } diff --git a/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap b/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap index f0e4010292766e..42e271c2c2b1f1 100644 --- a/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap +++ b/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap @@ -1,83 +1,69 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Webpack \`combine-assets\` should produce expected output: Asset file should match snapshot 1`] = `"<?php return array('fileA.js' => array('dependencies' => array('lodash', 'wp-blob'), 'version' => '2ac177723ec97b400d9b7c46f5270974'), 'fileB.js' => array('dependencies' => array('wp-token-list'), 'version' => 'bddb08fb8608759738528b9de111454d'));"`; +exports[`Webpack \`combine-assets\` should produce expected output: Asset file should match snapshot 1`] = `"<?php return array('fileA.js' => array('dependencies' => array('lodash', 'wp-blob'), 'version' => 'a3da22ced6876ec052d2861d383960fc'), 'fileB.js' => array('dependencies' => array('wp-token-list'), 'version' => '1a2b3802cc39dc1a607ecc4d0b23fcb0'));"`; exports[`Webpack \`combine-assets\` should produce expected output: External modules should match snapshot 1`] = ` Array [ Object { - "externalType": "this", - "request": Object { - "this": Array [ - "wp", - "blob", - ], - }, - "userRequest": "@wordpress/blob", + "externalType": "window", + "request": "lodash", + "userRequest": "lodash", }, Object { - "externalType": "this", - "request": Object { - "this": Array [ - "wp", - "tokenList", - ], - }, - "userRequest": "@wordpress/token-list", + "externalType": "window", + "request": Array [ + "wp", + "blob", + ], + "userRequest": "@wordpress/blob", }, Object { - "externalType": "this", - "request": Object { - "this": "lodash", - }, - "userRequest": "lodash", + "externalType": "window", + "request": Array [ + "wp", + "tokenList", + ], + "userRequest": "@wordpress/token-list", }, ] `; -exports[`Webpack \`dynamic-import\` should produce expected output: Asset file should match snapshot 1`] = `"<?php return array('dependencies' => array('lodash', 'wp-blob'), 'version' => '5d8e58fe98bc4c6277a76ece11fcb8b7');"`; +exports[`Webpack \`dynamic-import\` should produce expected output: Asset file should match snapshot 1`] = `"<?php return array('dependencies' => array('lodash', 'wp-blob'), 'version' => 'd5c613274c346f46e16ab4d320fc01e6');"`; exports[`Webpack \`dynamic-import\` should produce expected output: External modules should match snapshot 1`] = ` Array [ Object { - "externalType": "this", - "request": Object { - "this": Array [ - "wp", - "blob", - ], - }, - "userRequest": "@wordpress/blob", + "externalType": "window", + "request": "lodash", + "userRequest": "lodash", }, Object { - "externalType": "this", - "request": Object { - "this": "lodash", - }, - "userRequest": "lodash", + "externalType": "window", + "request": Array [ + "wp", + "blob", + ], + "userRequest": "@wordpress/blob", }, ] `; -exports[`Webpack \`function-output-filename\` should produce expected output: Asset file should match snapshot 1`] = `"<?php return array('dependencies' => array('lodash', 'wp-blob'), 'version' => '407eacfb62231e58aaca5eec87e63725');"`; +exports[`Webpack \`function-output-filename\` should produce expected output: Asset file should match snapshot 1`] = `"<?php return array('dependencies' => array('lodash', 'wp-blob'), 'version' => 'f6dc46e27c3a9e7338ed4fa9fdf8f606');"`; exports[`Webpack \`function-output-filename\` should produce expected output: External modules should match snapshot 1`] = ` Array [ Object { - "externalType": "this", - "request": Object { - "this": Array [ - "wp", - "blob", - ], - }, - "userRequest": "@wordpress/blob", + "externalType": "window", + "request": "lodash", + "userRequest": "lodash", }, Object { - "externalType": "this", - "request": Object { - "this": "lodash", - }, - "userRequest": "lodash", + "externalType": "window", + "request": Array [ + "wp", + "blob", + ], + "userRequest": "@wordpress/blob", }, ] `; @@ -90,140 +76,90 @@ exports[`Webpack \`no-deps\` should produce expected output: Asset file should m exports[`Webpack \`no-deps\` should produce expected output: External modules should match snapshot 1`] = `Array []`; -exports[`Webpack \`output-format-json\` should produce expected output: Asset file should match snapshot 1`] = `"{\\"dependencies\\":[\\"lodash\\"],\\"version\\":\\"0e6d34ea09104a64e6184f524b48ad65\\"}"`; +exports[`Webpack \`output-format-json\` should produce expected output: Asset file should match snapshot 1`] = `"{\\"dependencies\\":[\\"lodash\\"],\\"version\\":\\"aeb5066a5e17aea01932c77baf342372\\"}"`; exports[`Webpack \`output-format-json\` should produce expected output: External modules should match snapshot 1`] = ` Array [ Object { - "externalType": "this", - "request": Object { - "this": "lodash", - }, + "externalType": "window", + "request": "lodash", "userRequest": "lodash", }, ] `; -exports[`Webpack \`overrides\` should produce expected output: Asset file should match snapshot 1`] = `"<?php return array('dependencies' => array('wp-blob', 'wp-script-handle-for-rxjs', 'wp-url'), 'version' => '2fd24c89b50c763f2e2dcc02a6933c16');"`; +exports[`Webpack \`overrides\` should produce expected output: Asset file should match snapshot 1`] = `"<?php return array('dependencies' => array('wp-blob', 'wp-script-handle-for-rxjs', 'wp-url'), 'version' => '67d711ce8940ddb82e7f264f61a5a3d9');"`; exports[`Webpack \`overrides\` should produce expected output: External modules should match snapshot 1`] = ` Array [ Object { - "externalType": "this", - "request": Object { - "this": Array [ - "wp", - "blob", - ], - }, - "userRequest": "@wordpress/blob", - }, - Object { - "externalType": "this", - "request": Object { - "this": Array [ - "wp", - "url", - ], - }, - "userRequest": "@wordpress/url", + "externalType": "window", + "request": "rxjs", + "userRequest": "rxjs", }, Object { - "externalType": "this", - "request": Object { - "this": Array [ - "rxjs", - "operators", - ], - }, + "externalType": "window", + "request": Array [ + "rxjs", + "operators", + ], "userRequest": "rxjs/operators", }, Object { - "externalType": "this", - "request": Object { - "this": "rxjs", - }, - "userRequest": "rxjs", - }, -] -`; - -exports[`Webpack \`with-externs\` should produce expected output: Asset file should match snapshot 1`] = `"<?php return array('dependencies' => array('wp-url'), 'version' => 'e987036543f42978608ac27e589b5fe3');"`; - -exports[`Webpack \`with-externs\` should produce expected output: External modules should match snapshot 1`] = ` -Array [ - Object { - "externalType": "var", - "request": "wp.blob", + "externalType": "window", + "request": Array [ + "wp", + "blob", + ], "userRequest": "@wordpress/blob", }, Object { - "externalType": "this", - "request": Object { - "this": Array [ - "wp", - "url", - ], - }, + "externalType": "window", + "request": Array [ + "wp", + "url", + ], "userRequest": "@wordpress/url", }, - Object { - "externalType": "var", - "request": "rxjs.operators", - "userRequest": "rxjs/operators", - }, - Object { - "externalType": "var", - "request": "rxjs", - "userRequest": "rxjs", - }, ] `; -exports[`Webpack \`wordpress\` should produce expected output: Asset file should match snapshot 1`] = `"<?php return array('dependencies' => array('lodash', 'wp-blob'), 'version' => 'f6f0d227edbcb02382070c0efac33b51');"`; +exports[`Webpack \`wordpress\` should produce expected output: Asset file should match snapshot 1`] = `"<?php return array('dependencies' => array('lodash', 'wp-blob'), 'version' => '202b3ce17cfd72799ce45e7efa04d83c');"`; exports[`Webpack \`wordpress\` should produce expected output: External modules should match snapshot 1`] = ` Array [ Object { - "externalType": "this", - "request": Object { - "this": Array [ - "wp", - "blob", - ], - }, - "userRequest": "@wordpress/blob", + "externalType": "window", + "request": "lodash", + "userRequest": "lodash", }, Object { - "externalType": "this", - "request": Object { - "this": "lodash", - }, - "userRequest": "lodash", + "externalType": "window", + "request": Array [ + "wp", + "blob", + ], + "userRequest": "@wordpress/blob", }, ] `; -exports[`Webpack \`wordpress-require\` should produce expected output: Asset file should match snapshot 1`] = `"<?php return array('dependencies' => array('lodash', 'wp-blob'), 'version' => '31a7631b4838830b14b5ab10e17f3e0f');"`; +exports[`Webpack \`wordpress-require\` should produce expected output: Asset file should match snapshot 1`] = `"<?php return array('dependencies' => array('lodash', 'wp-blob'), 'version' => '47b0c1540a1caf08b8931e4a4328db04');"`; exports[`Webpack \`wordpress-require\` should produce expected output: External modules should match snapshot 1`] = ` Array [ Object { - "externalType": "this", - "request": Object { - "this": Array [ - "wp", - "blob", - ], - }, - "userRequest": "@wordpress/blob", + "externalType": "window", + "request": "lodash", + "userRequest": "lodash", }, Object { - "externalType": "this", - "request": Object { - "this": "lodash", - }, - "userRequest": "lodash", + "externalType": "window", + "request": Array [ + "wp", + "blob", + ], + "userRequest": "@wordpress/blob", }, ] `; diff --git a/packages/dependency-extraction-webpack-plugin/test/build.js b/packages/dependency-extraction-webpack-plugin/test/build.js index d6df018b6f3296..9f89564543608f 100644 --- a/packages/dependency-extraction-webpack-plugin/test/build.js +++ b/packages/dependency-extraction-webpack-plugin/test/build.js @@ -34,8 +34,8 @@ describe.each( configFixtures )( 'Webpack `%s`', ( configCase ) => { mode: 'production', optimization: { minimize: false, - namedChunks: true, - namedModules: true, + chunkIds: 'named', + moduleIds: 'named', }, output: {}, }, @@ -66,10 +66,18 @@ describe.each( configFixtures )( 'Webpack `%s`', ( configCase ) => { ).toMatchSnapshot( 'Asset file should match snapshot' ); } ); + const compareByModuleIdentifier = ( m1, m2 ) => { + const i1 = m1.identifier(); + const i2 = m2.identifier(); + if ( i1 < i2 ) return -1; + if ( i1 > i2 ) return 1; + return 0; + }; + // Webpack stats external modules should match. const externalModules = stats.compilation.modules - .filter( ( { external } ) => external ) - .sort() + .filter( ( { externalType } ) => externalType ) + .sort( compareByModuleIdentifier ) .map( ( module ) => ( { externalType: module.externalType, request: module.request, diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/with-externs/index.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/with-externs/index.js deleted file mode 100644 index 21fda20b8d8fad..00000000000000 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/with-externs/index.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * WordPress dependencies - */ -import { isBlobURL } from '@wordpress/blob'; -import { isURL } from '@wordpress/url'; - -/** - * External dependencies - */ -import { range } from 'rxjs'; -import { map, filter } from 'rxjs/operators'; - -range( 1, 200 ) - .pipe( - filter( isBlobURL ), - map( ( x ) => x + x ) - ) - .subscribe( ( x ) => isURL( x ) ); diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/with-externs/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/with-externs/webpack.config.js deleted file mode 100644 index a1e17a2c06e2f3..00000000000000 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/with-externs/webpack.config.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -const DependencyExtractionWebpackPlugin = require( '../../..' ); - -module.exports = { - externals: { - '@wordpress/blob': 'wp.blob', - 'rxjs/operators': 'rxjs.operators', - rxjs: true, - }, - plugins: [ new DependencyExtractionWebpackPlugin() ], -}; diff --git a/packages/deprecated/CHANGELOG.md b/packages/deprecated/CHANGELOG.md index bcfa0cb3857bbe..9e7a628d7594d3 100644 --- a/packages/deprecated/CHANGELOG.md +++ b/packages/deprecated/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### New Feature + +- Include TypeScript type declarations ([#26429](https://github.com/WordPress/gutenberg/pull/26429)) + ## 2.6.0 (2019-08-29) ### Bug Fix diff --git a/packages/deprecated/README.md b/packages/deprecated/README.md index 38e5543f0834df..756d6662159e23 100644 --- a/packages/deprecated/README.md +++ b/packages/deprecated/README.md @@ -54,12 +54,12 @@ deprecated( 'Eating meat', { _Parameters_ - _feature_ `string`: Name of the deprecated feature. -- _options_ `?Object`: Personalisation options -- _options.version_ `?string`: Version in which the feature will be removed. -- _options.alternative_ `?string`: Feature to use instead -- _options.plugin_ `?string`: Plugin name if it's a plugin feature -- _options.link_ `?string`: Link to documentation -- _options.hint_ `?string`: Additional message to help transition away from the deprecated feature. +- _options_ `[Object]`: Personalisation options +- _options.version_ `[string]`: Version in which the feature will be removed. +- _options.alternative_ `[string]`: Feature to use instead +- _options.plugin_ `[string]`: Plugin name if it's a plugin feature +- _options.link_ `[string]`: Link to documentation +- _options.hint_ `[string]`: Additional message to help transition away from the deprecated feature. <a name="logged" href="#logged">#</a> **logged** @@ -68,7 +68,7 @@ message is only logged once. _Type_ -- `Object` +- `Record<string,(|undefined)>` <!-- END TOKEN(Autogenerated API docs) --> diff --git a/packages/deprecated/package.json b/packages/deprecated/package.json index b6c895c48ebaae..d2b95c4e11b6fd 100644 --- a/packages/deprecated/package.json +++ b/packages/deprecated/package.json @@ -21,9 +21,10 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "types": "build-types", "sideEffects": false, "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/hooks": "file:../hooks" }, "publishConfig": { diff --git a/packages/deprecated/src/index.js b/packages/deprecated/src/index.js index 2c067a82916d55..13f11a24a0df77 100644 --- a/packages/deprecated/src/index.js +++ b/packages/deprecated/src/index.js @@ -7,20 +7,20 @@ import { doAction } from '@wordpress/hooks'; * Object map tracking messages which have been logged, for use in ensuring a * message is only logged once. * - * @type {Object} + * @type {Record<string,true|undefined>} */ export const logged = Object.create( null ); /** * Logs a message to notify developers about a deprecated feature. * - * @param {string} feature Name of the deprecated feature. - * @param {?Object} options Personalisation options - * @param {?string} options.version Version in which the feature will be removed. - * @param {?string} options.alternative Feature to use instead - * @param {?string} options.plugin Plugin name if it's a plugin feature - * @param {?string} options.link Link to documentation - * @param {?string} options.hint Additional message to help transition away from the deprecated feature. + * @param {string} feature Name of the deprecated feature. + * @param {Object} [options] Personalisation options + * @param {string} [options.version] Version in which the feature will be removed. + * @param {string} [options.alternative] Feature to use instead + * @param {string} [options.plugin] Plugin name if it's a plugin feature + * @param {string} [options.link] Link to documentation + * @param {string} [options.hint] Additional message to help transition away from the deprecated feature. * * @example * ```js diff --git a/packages/dependency-extraction-webpack-plugin/tsconfig.json b/packages/deprecated/tsconfig.json similarity index 53% rename from packages/dependency-extraction-webpack-plugin/tsconfig.json rename to packages/deprecated/tsconfig.json index 426ab13d0aa8f6..9d3c2f54d7b7e6 100644 --- a/packages/dependency-extraction-webpack-plugin/tsconfig.json +++ b/packages/deprecated/tsconfig.json @@ -1,8 +1,9 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "lib", + "rootDir": "src", "declarationDir": "build-types" }, - "include": [ "lib/**/*" ] + "references": [ { "path": "../hooks" } ], + "include": [ "src/**/*" ] } diff --git a/packages/docgen/package.json b/packages/docgen/package.json index 7262827594e7b0..729b1c63bfbf05 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -23,7 +23,7 @@ "docgen": "./bin/cli.js" }, "dependencies": { - "@babel/core": "^7.11.0", + "@babel/core": "^7.12.9", "doctrine": "^2.1.0", "lodash": "^4.17.19", "mdast-util-inject": "1.1.0", diff --git a/packages/dom-ready/package.json b/packages/dom-ready/package.json index 41bab823616a7b..b8b205a296b2d3 100644 --- a/packages/dom-ready/package.json +++ b/packages/dom-ready/package.json @@ -24,7 +24,7 @@ "types": "build-types", "sideEffects": false, "dependencies": { - "@babel/runtime": "^7.11.2" + "@babel/runtime": "^7.12.5" }, "publishConfig": { "access": "public" diff --git a/packages/dom/README.md b/packages/dom/README.md index d927dc887a09fc..d888a810f109d9 100644 --- a/packages/dom/README.md +++ b/packages/dom/README.md @@ -74,6 +74,18 @@ _Returns_ Object grouping `focusable` and `tabbable` utils under the keys with the same name. +<a name="getFilesFromDataTransfer" href="#getFilesFromDataTransfer">#</a> **getFilesFromDataTransfer** + +Gets all files from a DataTransfer object. + +_Parameters_ + +- _dataTransfer_ `DataTransfer`: DataTransfer object to inspect. + +_Returns_ + +- `Array<Object>`: An array containing all files. + <a name="getOffsetParent" href="#getOffsetParent">#</a> **getOffsetParent** Returns the closest positioned element, or null under any of the conditions diff --git a/packages/dom/package.json b/packages/dom/package.json index 15160fd48dc8c9..6fc97a67c81e9d 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -24,7 +24,7 @@ "react-native": "src/index", "sideEffects": false, "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "lodash": "^4.17.19" }, "publishConfig": { diff --git a/packages/dom/src/data-transfer.js b/packages/dom/src/data-transfer.js new file mode 100644 index 00000000000000..6052326c2c2213 --- /dev/null +++ b/packages/dom/src/data-transfer.js @@ -0,0 +1,28 @@ +/** + * Gets all files from a DataTransfer object. + * + * @param {DataTransfer} dataTransfer DataTransfer object to inspect. + * + * @return {Object[]} An array containing all files. + */ +export function getFilesFromDataTransfer( dataTransfer ) { + const files = [ ...dataTransfer.files ]; + + Array.from( dataTransfer.items ).forEach( ( item ) => { + const file = item.getAsFile(); + + if ( + file && + ! files.find( + ( { name, type, size } ) => + name === file.name && + type === file.type && + size === file.size + ) + ) { + files.push( file ); + } + } ); + + return files; +} diff --git a/packages/dom/src/dom.js b/packages/dom/src/dom.js index 33bbbc1e3bc55c..84f88108e6a227 100644 --- a/packages/dom/src/dom.js +++ b/packages/dom/src/dom.js @@ -12,6 +12,25 @@ function getComputedStyle( node ) { return node.ownerDocument.defaultView.getComputedStyle( node ); } +/** + * Gets the height of the range without ignoring zero width rectangles, which + * some browsers ignore when creating a union. + * + * @param {Range} range The range to check. + */ +function getRangeHeight( range ) { + const rects = Array.from( range.getClientRects() ); + + if ( ! rects.length ) { + return; + } + + const highestTop = Math.min( ...rects.map( ( { top } ) => top ) ); + const lowestBottom = Math.max( ...rects.map( ( { bottom } ) => bottom ) ); + + return lowestBottom - highestTop; +} + /** * Returns true if the given selection object is in the forward direction, or * false otherwise. @@ -88,73 +107,52 @@ function isEdge( container, isReverse, onlyVertical ) { return false; } - const originalRange = selection.getRangeAt( 0 ); - const range = originalRange.cloneRange(); + const range = selection.getRangeAt( 0 ); + const collapsedRange = range.cloneRange(); const isForward = isSelectionForward( selection ); const isCollapsed = selection.isCollapsed; // Collapse in direction of selection. if ( ! isCollapsed ) { - range.collapse( ! isForward ); + collapsedRange.collapse( ! isForward ); } + const collapsedRangeRect = getRectangleFromRange( collapsedRange ); const rangeRect = getRectangleFromRange( range ); - if ( ! rangeRect ) { + if ( ! collapsedRangeRect || ! rangeRect ) { return false; } - const computedStyle = getComputedStyle( container ); - const lineHeight = parseInt( computedStyle.lineHeight, 10 ) || 0; - // Only consider the multiline selection at the edge if the direction is - // towards the edge. + // towards the edge. The selection is multiline if it is taller than the + // collapsed selection. if ( ! isCollapsed && - rangeRect.height > lineHeight && + getRangeHeight( range ) > collapsedRangeRect.height && isForward === isReverse ) { return false; } - const padding = - parseInt( - computedStyle[ `padding${ isReverse ? 'Top' : 'Bottom' }` ], - 10 - ) || 0; - - // Calculate a buffer that is half the line height. In some browsers, the - // selection rectangle may not fill the entire height of the line, so we add - // 3/4 the line height to the selection rectangle to ensure that it is well - // over its line boundary. - const buffer = ( 3 * parseInt( lineHeight, 10 ) ) / 4; - const containerRect = container.getBoundingClientRect(); - const originalRangeRect = getRectangleFromRange( originalRange ); - const verticalEdge = isReverse - ? containerRect.top + padding > originalRangeRect.top - buffer - : containerRect.bottom - padding < originalRangeRect.bottom + buffer; - - if ( ! verticalEdge ) { - return false; - } - - if ( onlyVertical ) { - return true; - } - // In the case of RTL scripts, the horizontal edge is at the opposite side. - const { direction } = computedStyle; + const { direction } = getComputedStyle( container ); const isReverseDir = direction === 'rtl' ? ! isReverse : isReverse; - // To calculate the horizontal position, we insert a test range and see if - // this test range has the same horizontal position. This method proves to - // be better than a DOM-based calculation, because it ignores empty text - // nodes and a trailing line break element. In other words, we need to check - // visual positioning, not DOM positioning. + const containerRect = container.getBoundingClientRect(); + + // To check if a selection is at the edge, we insert a test selection at the + // edge of the container and check if the selections have the same vertical + // or horizontal position. If they do, the selection is at the edge. + // This method proves to be better than a DOM-based calculation for the + // horizontal edge, since it ignores empty textnodes and a trailing line + // break element. In other words, we need to check visual positioning, not + // DOM positioning. + // It also proves better than using the computed style for the vertical + // edge, because we cannot know the padding and line height reliably in + // pixels. `getComputedStyle` may return a value with different units. const x = isReverseDir ? containerRect.left + 1 : containerRect.right - 1; - const y = isReverse - ? containerRect.top + buffer - : containerRect.bottom - buffer; + const y = isReverse ? containerRect.top + 1 : containerRect.bottom - 1; const testRange = hiddenCaretRangeFromPoint( ownerDocument, x, @@ -166,11 +164,25 @@ function isEdge( container, isReverse, onlyVertical ) { return false; } - const side = isReverseDir ? 'left' : 'right'; const testRect = getRectangleFromRange( testRange ); + if ( ! testRect ) { + return false; + } + + const verticalSide = isReverse ? 'top' : 'bottom'; + const horizontalSide = isReverseDir ? 'left' : 'right'; + const verticalDiff = testRect[ verticalSide ] - rangeRect[ verticalSide ]; + const horizontalDiff = + testRect[ horizontalSide ] - collapsedRangeRect[ horizontalSide ]; + // Allow the position to be 1px off. - return Math.abs( testRect[ side ] - rangeRect[ side ] ) <= 1; + const hasVerticalDiff = Math.abs( verticalDiff ) <= 1; + const hasHorizontalDiff = Math.abs( horizontalDiff ) <= 1; + + return onlyVertical + ? hasVerticalDiff + : hasVerticalDiff && hasHorizontalDiff; } /** diff --git a/packages/dom/src/index.js b/packages/dom/src/index.js index f1b1e0180d0ccb..42a72944a95904 100644 --- a/packages/dom/src/index.js +++ b/packages/dom/src/index.js @@ -12,3 +12,4 @@ export const focus = { focusable, tabbable }; export * from './dom'; export * from './phrasing-content'; +export * from './data-transfer'; diff --git a/packages/e2e-test-utils/CHANGELOG.md b/packages/e2e-test-utils/CHANGELOG.md index ada28c012f9736..b12671da6d1be1 100644 --- a/packages/e2e-test-utils/CHANGELOG.md +++ b/packages/e2e-test-utils/CHANGELOG.md @@ -2,54 +2,58 @@ ## Unreleased +### Nee Features + +- Added `clickMenuItem` - clicks the item that matches the label in the opened menu. + ## 4.5.0 (2020-04-15) ### Enhancements -- `visitAdminPage` will now throw an error (emit a test failure) when there are unexpected errors on hte page. +- `visitAdminPage` will now throw an error (emit a test failure) when there are unexpected errors on hte page. ### New Features -- Added `getPageError` function, returning a promise which resolves to an error message present in the page, if any exists. +- Added `getPageError` function, returning a promise which resolves to an error message present in the page, if any exists. ## 4.0.0 (2019-12-19) ### Breaking Changes -- The disableNavigationMode utility was removed. By default, the editor is in edit mode now. +- The disableNavigationMode utility was removed. By default, the editor is in edit mode now. ### Improvements -- `setBrowserViewport` accepts an object of `width`, `height` values, to assign a viewport of arbitrary size. +- `setBrowserViewport` accepts an object of `width`, `height` values, to assign a viewport of arbitrary size. ## 3.0.0 (2019-11-14) ### Breaking Changes -- The util function `enableExperimentalFeatures` was removed. It is now available for internal usage in the `e2e-tests` package. +- The util function `enableExperimentalFeatures` was removed. It is now available for internal usage in the `e2e-tests` package. ## 2.0.0 (2019-05-21) ### Requirements -- The minimum version of Gutenberg `5.6.0` or the minimum version of WordPress `5.2.0`. +- The minimum version of Gutenberg `5.6.0` or the minimum version of WordPress `5.2.0`. ### Bug Fixes -- WordPress 5.2: Fix a false positive build failure caused by Dashicons font file. -- WordPress 5.2: Fix a test failure for Classic Block media insertion caused by a change in tooltips text ([rWP45066](https://core.trac.wordpress.org/changeset/45066)). +- WordPress 5.2: Fix a false positive build failure caused by Dashicons font file. +- WordPress 5.2: Fix a test failure for Classic Block media insertion caused by a change in tooltips text ([rWP45066](https://core.trac.wordpress.org/changeset/45066)). ## 1.1.0 (2019-03-20) ### New Features -- New Function: `getAllBlockInserterItemTitles` - Returns an array of strings with all inserter item titles. -- New Function: `openAllBlockInserterCategories` - Opens all block inserter categories. -- New Function: `getAllBlockInserterItemTitles` - Opens the global block inserter. +- New Function: `getAllBlockInserterItemTitles` - Returns an array of strings with all inserter item titles. +- New Function: `openAllBlockInserterCategories` - Opens all block inserter categories. +- New Function: `getAllBlockInserterItemTitles` - Opens the global block inserter. ### Requirements -- The minimum version of Gutenberg `5.3.0` or the minimum version of WordPress `5.2.0`. +- The minimum version of Gutenberg `5.3.0` or the minimum version of WordPress `5.2.0`. ## 1.0.0 (2019-03-06) @@ -59,4 +63,4 @@ ### Requirements -- The minimum version of Gutenberg `5.2.0` or the minimum version of WordPress `5.2.0`. +- The minimum version of Gutenberg `5.2.0` or the minimum version of WordPress `5.2.0`. diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md index 6b5366797d7984..29e20742248576 100644 --- a/packages/e2e-test-utils/README.md +++ b/packages/e2e-test-utils/README.md @@ -24,6 +24,14 @@ _Parameters_ - _slug_ `string`: Plugin slug. +<a name="activateTheme" href="#activateTheme">#</a> **activateTheme** + +Activates an installed theme. + +_Parameters_ + +- _slug_ `string`: Theme slug. + <a name="arePrePublishChecksEnabled" href="#arePrePublishChecksEnabled">#</a> **arePrePublishChecksEnabled** Verifies if publish checks are enabled. @@ -32,6 +40,18 @@ _Returns_ - `Promise<boolean>`: Boolean which represents the state of prepublish checks. +<a name="changeSiteTimezone" href="#changeSiteTimezone">#</a> **changeSiteTimezone** + +Visits general settings page and changes the timezone to the given value. + +_Parameters_ + +- _timezone_ `string`: Value of the timezone to set. + +_Returns_ + +- `string`: Value of the previous timezone. + <a name="clearLocalStorage" href="#clearLocalStorage">#</a> **clearLocalStorage** Clears the local storage. @@ -57,6 +77,14 @@ _Parameters_ - _buttonText_ `string`: The text that appears on the button to click. +<a name="clickMenuItem" href="#clickMenuItem">#</a> **clickMenuItem** + +Searches for an item in the menu with the text provided and clicks it. + +_Parameters_ + +- _label_ `string`: The label to search the menu item for. + <a name="clickOnCloseModalButton" href="#clickOnCloseModalButton">#</a> **clickOnCloseModalButton** Click on the close button of an open modal. @@ -147,6 +175,17 @@ _Parameters_ - _slug_ `string`: Plugin slug. +<a name="deleteTheme" href="#deleteTheme">#</a> **deleteTheme** + +Deletes a theme from the site, activating another theme if necessary. + +_Parameters_ + +- _slug_ `string`: Theme slug. +- _settings_ `?Object`: Optional settings object. +- _settings.newThemeSlug_ `?string`: A theme to switch to if the theme to delete is active. Required if the theme to delete is active. +- _settings.newThemeSearchTerm_ `?string`: A search term to use if the new theme is not findable by its slug. + <a name="disableFocusLossObservation" href="#disableFocusLossObservation">#</a> **disableFocusLossObservation** Removes the focus loss listener that `enableFocusLossObservation()` adds. @@ -340,6 +379,16 @@ _Parameters_ - _slug_ `string`: Plugin slug. - _searchTerm_ `?string`: If the plugin is not findable by its slug use an alternative term to search. +<a name="installTheme" href="#installTheme">#</a> **installTheme** + +Installs a theme from the WP.org repository. + +_Parameters_ + +- _slug_ `string`: Theme slug. +- _settings_ `?Object`: Optional settings object. +- _settings.searchTerm_ `?string`: Search term to use if the theme is not findable by its slug. + <a name="isCurrentURL" href="#isCurrentURL">#</a> **isCurrentURL** Checks if current URL is a WordPress path. @@ -365,6 +414,18 @@ _Returns_ Undocumented declaration. +<a name="isThemeInstalled" href="#isThemeInstalled">#</a> **isThemeInstalled** + +Checks whether a theme exists on the site. + +_Parameters_ + +- _slug_ `string`: Theme slug to check. + +_Returns_ + +- `boolean`: Whether the theme exists. + <a name="loginUser" href="#loginUser">#</a> **loginUser** Performs log in with specified username and password. @@ -397,6 +458,18 @@ Clicks on the button in the header which opens Document Settings sidebar when it Opens the global block inserter. +<a name="openPreviewPage" href="#openPreviewPage">#</a> **openPreviewPage** + +Opens the preview page of an edited post. + +_Parameters_ + +- _editorPage_ `Page`: puppeteer editor page. + +_Returns_ + +- `Page`: preview page. + <a name="openPublishPanel" href="#openPublishPanel">#</a> **openPublishPanel** Opens the publish panel. @@ -487,6 +560,17 @@ _Parameters_ - _viewport_ `WPViewport`: Viewport name or dimensions object to assign. +<a name="setClipboardData" href="#setClipboardData">#</a> **setClipboardData** + +Sets the clipboard data that can be pasted with +`pressKeyWithModifier( 'primary', 'v' )`. + +_Parameters_ + +- _$1_ `Object`: Options. +- _$1.plainText_ `string`: Plain text to set. +- _$1.html_ `string`: HTML to set. + <a name="setPostContent" href="#setPostContent">#</a> **setPostContent** Sets code editor content @@ -551,6 +635,10 @@ running the test is not already the admin user). Switches the current user to whichever user we should be running the tests as (if we're not already that user). +<a name="toggleGlobalBlockInserter" href="#toggleGlobalBlockInserter">#</a> **toggleGlobalBlockInserter** + +Toggles the global inserter. + <a name="toggleMoreMenu" href="#toggleMoreMenu">#</a> **toggleMoreMenu** Toggles the More Menu. @@ -583,6 +671,7 @@ Navigates to the post listing screen and bulk-trashes any posts which exist. _Parameters_ - _postType_ `string`: String slug for type of post to trash. +- _postStatus_ `string`: String status of posts to trash. _Returns_ diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index fcfcafc1846596..6d9b007a9c0062 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-test-utils", - "version": "4.15.0", + "version": "4.15.1", "description": "End-To-End (E2E) test utils for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,7 +29,7 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/keycodes": "file:../keycodes", "@wordpress/url": "file:../url", "lodash": "^4.17.19", diff --git a/packages/e2e-test-utils/src/activate-theme.js b/packages/e2e-test-utils/src/activate-theme.js new file mode 100644 index 00000000000000..5c3c20226297fd --- /dev/null +++ b/packages/e2e-test-utils/src/activate-theme.js @@ -0,0 +1,28 @@ +/** + * Internal dependencies + */ +import { switchUserToAdmin } from './switch-user-to-admin'; +import { switchUserToTest } from './switch-user-to-test'; +import { visitAdminPage } from './visit-admin-page'; + +/** + * Activates an installed theme. + * + * @param {string} slug Theme slug. + */ +export async function activateTheme( slug ) { + await switchUserToAdmin(); + await visitAdminPage( 'themes.php' ); + + const activateButton = await page.$( + `div[data-slug="${ slug }"] .button.activate` + ); + if ( ! activateButton ) { + switchUserToTest(); + return; + } + + await page.click( `div[data-slug="${ slug }"] .button.activate` ); + await page.waitForSelector( `div[data-slug="${ slug }"].active` ); + await switchUserToTest(); +} diff --git a/packages/e2e-test-utils/src/change-site-timezone.js b/packages/e2e-test-utils/src/change-site-timezone.js new file mode 100644 index 00000000000000..8389eca034a37d --- /dev/null +++ b/packages/e2e-test-utils/src/change-site-timezone.js @@ -0,0 +1,29 @@ +/** + * Internal dependencies + */ +import { visitAdminPage } from './visit-admin-page'; +import { switchUserToAdmin } from './switch-user-to-admin'; +import { switchUserToTest } from './switch-user-to-test'; + +/** + * Visits general settings page and changes the timezone to the given value. + * + * @param {string} timezone Value of the timezone to set. + * + * @return {string} Value of the previous timezone. + */ +export async function changeSiteTimezone( timezone ) { + await switchUserToAdmin(); + await visitAdminPage( 'options-general.php' ); + + const oldTimezone = await page.$eval( + '#timezone_string', + ( element ) => element.options[ element.selectedIndex ].text + ); + await page.select( '#timezone_string', timezone ); + await page.click( '#submit' ); + + await switchUserToTest(); + + return oldTimezone; +} diff --git a/packages/e2e-test-utils/src/click-block-toolbar-button.js b/packages/e2e-test-utils/src/click-block-toolbar-button.js index dc27849e36d5e6..3aa431bfe29ff9 100644 --- a/packages/e2e-test-utils/src/click-block-toolbar-button.js +++ b/packages/e2e-test-utils/src/click-block-toolbar-button.js @@ -15,12 +15,13 @@ export async function clickBlockToolbarButton( label, type = 'ariaLabel' ) { let button; if ( type === 'ariaLabel' ) { - const BUTTON_SELECTOR = `.${ BLOCK_TOOLBAR_SELECTOR } button[aria-label="${ label }"]`; - button = await page.waitForSelector( BUTTON_SELECTOR ); + button = await page.waitForSelector( + `.${ BLOCK_TOOLBAR_SELECTOR } button[aria-label="${ label }"]` + ); } if ( type === 'content' ) { - [ button ] = await page.$x( + button = await page.waitForXPath( `//*[@class='${ BLOCK_TOOLBAR_SELECTOR }']//button[contains(text(), '${ label }')]` ); } diff --git a/packages/e2e-test-utils/src/click-menu-item.js b/packages/e2e-test-utils/src/click-menu-item.js new file mode 100644 index 00000000000000..e4a29eacf39e60 --- /dev/null +++ b/packages/e2e-test-utils/src/click-menu-item.js @@ -0,0 +1,18 @@ +/** + * External dependencies + */ +import { first } from 'lodash'; + +/** + * Searches for an item in the menu with the text provided and clicks it. + * + * @param {string} label The label to search the menu item for. + */ +export async function clickMenuItem( label ) { + const elementToClick = first( + await page.$x( + `//div[@role="menu"]//span[contains(concat(" ", @class, " "), " components-menu-item__item ")][contains(text(), "${ label }")]` + ) + ); + await elementToClick.click(); +} diff --git a/packages/e2e-test-utils/src/click-on-more-menu-item.js b/packages/e2e-test-utils/src/click-on-more-menu-item.js index 564ef6b715e6cb..04d3709ac0e196 100644 --- a/packages/e2e-test-utils/src/click-on-more-menu-item.js +++ b/packages/e2e-test-utils/src/click-on-more-menu-item.js @@ -17,20 +17,10 @@ export async function clickOnMoreMenuItem( buttonLabel ) { await toggleMoreMenu(); const moreMenuContainerSelector = '//*[contains(concat(" ", @class, " "), " edit-post-more-menu__content ")]'; - let elementToClick = first( + const elementToClick = first( await page.$x( - `${ moreMenuContainerSelector }//button[contains(text(), "${ buttonLabel }")]` + `${ moreMenuContainerSelector }//span[contains(concat(" ", @class, " "), " components-menu-item__item ")][contains(text(), "${ buttonLabel }")]` ) ); - // If button is not found, the label should be on the info wrapper. - if ( ! elementToClick ) { - elementToClick = first( - await page.$x( - moreMenuContainerSelector + - '//button' + - `/*[contains(concat(" ", @class, " "), " components-menu-item__info-wrapper ")][contains(text(), "${ buttonLabel }")]` - ) - ); - } await elementToClick.click(); } diff --git a/packages/e2e-test-utils/src/create-new-post.js b/packages/e2e-test-utils/src/create-new-post.js index d47fe213c38e21..193468e5afacc1 100644 --- a/packages/e2e-test-utils/src/create-new-post.js +++ b/packages/e2e-test-utils/src/create-new-post.js @@ -58,5 +58,7 @@ export async function createNewPost( { .dispatch( 'core/edit-post' ) .toggleFeature( 'fullscreenMode' ) ); + + await page.waitForSelector( 'body:not(.is-fullscreen-mode)' ); } } diff --git a/packages/e2e-test-utils/src/delete-theme.js b/packages/e2e-test-utils/src/delete-theme.js new file mode 100644 index 00000000000000..074fcb92c9d82d --- /dev/null +++ b/packages/e2e-test-utils/src/delete-theme.js @@ -0,0 +1,51 @@ +/** + * Internal dependencies + */ +import { activateTheme } from './activate-theme'; +import { installTheme } from './install-theme'; +import { switchUserToAdmin } from './switch-user-to-admin'; +import { switchUserToTest } from './switch-user-to-test'; +import { visitAdminPage } from './visit-admin-page'; +import { isThemeInstalled } from './theme-installed'; + +/** + * Deletes a theme from the site, activating another theme if necessary. + * + * @param {string} slug Theme slug. + * @param {Object?} settings Optional settings object. + * @param {string?} settings.newThemeSlug A theme to switch to if the theme to delete is active. Required if the theme to delete is active. + * @param {string?} settings.newThemeSearchTerm A search term to use if the new theme is not findable by its slug. + */ +export async function deleteTheme( + slug, + { newThemeSlug, newThemeSearchTerm } = {} +) { + await switchUserToAdmin(); + + if ( newThemeSlug ) { + await installTheme( newThemeSlug, newThemeSearchTerm ); + await activateTheme( newThemeSlug ); + } else { + await visitAdminPage( 'themes.php' ); + } + + if ( ! ( await isThemeInstalled( slug ) ) ) { + await switchUserToTest(); + return; + } + + await page.click( `[data-slug="${ slug }"]` ); + await page.waitForSelector( '.theme-actions .delete-theme' ); + await page.click( '.theme-actions .delete-theme' ); + await page.waitForSelector( 'body:not(.modal-open)' ); + + // Wait for the theme to be removed from the page. + // eslint-disable-next-line no-restricted-syntax + await page.waitFor( + ( themeSlug ) => + ! document.querySelector( `[data-slug="${ themeSlug }"]` ), + slug + ); + + await switchUserToTest(); +} diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js index be50550b24bac4..c70f7fd15f69c7 100644 --- a/packages/e2e-test-utils/src/index.js +++ b/packages/e2e-test-utils/src/index.js @@ -1,14 +1,18 @@ export { activatePlugin } from './activate-plugin'; +export { activateTheme } from './activate-theme'; export { arePrePublishChecksEnabled } from './are-pre-publish-checks-enabled'; +export { changeSiteTimezone } from './change-site-timezone'; export { clearLocalStorage } from './clear-local-storage'; export { clickBlockAppender } from './click-block-appender'; export { clickBlockToolbarButton } from './click-block-toolbar-button'; export { clickButton } from './click-button'; +export { clickMenuItem } from './click-menu-item'; export { clickOnCloseModalButton } from './click-on-close-modal-button'; export { clickOnMoreMenuItem } from './click-on-more-menu-item'; export { createNewPost } from './create-new-post'; export { createURL } from './create-url'; export { deactivatePlugin } from './deactivate-plugin'; +export { deleteTheme } from './delete-theme'; export { disablePrePublishChecks } from './disable-pre-publish-checks'; export { dragAndResize } from './drag-and-resize'; export { enablePageDialogAccept } from './enable-page-dialog-accept'; @@ -34,8 +38,10 @@ export { insertBlockDirectoryBlock, openGlobalBlockInserter, closeGlobalBlockInserter, + toggleGlobalBlockInserter, } from './inserter'; export { installPlugin } from './install-plugin'; +export { installTheme } from './install-theme'; export { isCurrentURL } from './is-current-url'; export { isInDefaultBlock } from './is-in-default-block'; export { loginUser } from './login-user'; @@ -47,7 +53,10 @@ export { openDocumentSettingsSidebar } from './open-document-settings-sidebar'; export { openPublishPanel } from './open-publish-panel'; export { trashAllPosts } from './posts'; export { pressKeyTimes } from './press-key-times'; -export { pressKeyWithModifier } from './press-key-with-modifier'; +export { + pressKeyWithModifier, + setClipboardData, +} from './press-key-with-modifier'; export { publishPost } from './publish-post'; export { publishPostWithPrePublishChecksDisabled } from './publish-post-with-pre-publish-checks-disabled'; export { saveDraft } from './save-draft'; @@ -57,6 +66,7 @@ export { setPostContent } from './set-post-content'; export { switchEditorModeTo } from './switch-editor-mode-to'; export { switchUserToAdmin } from './switch-user-to-admin'; export { switchUserToTest } from './switch-user-to-test'; +export { isThemeInstalled } from './theme-installed'; export { toggleMoreMenu } from './toggle-more-menu'; export { toggleOfflineMode, isOfflineMode } from './offline-mode'; export { toggleScreenOption } from './toggle-screen-option'; @@ -65,5 +75,6 @@ export { uninstallPlugin } from './uninstall-plugin'; export { visitAdminPage } from './visit-admin-page'; export { waitForWindowDimensions } from './wait-for-window-dimensions'; export { showBlockToolbar } from './show-block-toolbar'; +export { openPreviewPage } from './preview'; export * from './mocks'; diff --git a/packages/e2e-test-utils/src/inserter.js b/packages/e2e-test-utils/src/inserter.js index 8c51fb306ee73f..c3d31299f3e42b 100644 --- a/packages/e2e-test-utils/src/inserter.js +++ b/packages/e2e-test-utils/src/inserter.js @@ -44,8 +44,10 @@ async function isGlobalInserterOpen() { ); } ); } - -async function toggleGlobalBlockInserter() { +/** + * Toggles the global inserter. + */ +export async function toggleGlobalBlockInserter() { await page.click( '.edit-post-header [aria-label="Add block"], .edit-site-header [aria-label="Add block"]' ); @@ -57,7 +59,9 @@ async function toggleGlobalBlockInserter() { async function waitForInserterCloseAndContentFocus() { await page.waitForFunction( () => document.body - .querySelector( '.block-editor-block-list__layout' ) + .querySelector( + '.interface-interface-skeleton__content .block-editor-block-list__layout' + ) .contains( document.activeElement ) ); } @@ -143,11 +147,9 @@ export async function insertBlock( searchTerm ) { */ export async function insertPattern( searchTerm ) { await searchForPattern( searchTerm ); - const insertButton = ( - await page.$x( - `//div[@role = 'button']//div[contains(text(), '${ searchTerm }')]` - ) - )[ 0 ]; + const insertButton = await page.waitForXPath( + `//div[@role = 'button']//div[contains(text(), '${ searchTerm }')]` + ); await insertButton.click(); // We should wait until the inserter closes and the focus moves to the content. await waitForInserterCloseAndContentFocus(); diff --git a/packages/e2e-test-utils/src/install-theme.js b/packages/e2e-test-utils/src/install-theme.js new file mode 100644 index 00000000000000..450ffd6aa98474 --- /dev/null +++ b/packages/e2e-test-utils/src/install-theme.js @@ -0,0 +1,42 @@ +/** + * Internal dependencies + */ +import { switchUserToAdmin } from './switch-user-to-admin'; +import { switchUserToTest } from './switch-user-to-test'; +import { visitAdminPage } from './visit-admin-page'; +import { isThemeInstalled } from './theme-installed'; + +/** + * Installs a theme from the WP.org repository. + * + * @param {string} slug Theme slug. + * @param {Object?} settings Optional settings object. + * @param {string?} settings.searchTerm Search term to use if the theme is not findable by its slug. + */ +export async function installTheme( slug, { searchTerm } = {} ) { + await switchUserToAdmin(); + + const installed = await isThemeInstalled( slug ); + if ( installed ) { + return; + } + + await visitAdminPage( + 'theme-install.php', + `search=${ encodeURIComponent( searchTerm || slug ) }` + ); + await page.waitForSelector( `div[data-slug="${ slug }"]` ); + + const activateLink = await page.$( + `div[data-slug="${ slug }"] .button.activate` + ); + if ( activateLink ) { + await switchUserToTest(); + return; + } + + await page.waitForSelector( `.theme-install[data-slug="${ slug }"]` ); + await page.click( `.theme-install[data-slug="${ slug }"]` ); + await page.waitForSelector( `.theme[data-slug="${ slug }"] .activate` ); + await switchUserToTest(); +} diff --git a/packages/e2e-test-utils/src/open-document-settings-sidebar.js b/packages/e2e-test-utils/src/open-document-settings-sidebar.js index ab6f797a4bacca..2ab5f773c3715f 100644 --- a/packages/e2e-test-utils/src/open-document-settings-sidebar.js +++ b/packages/e2e-test-utils/src/open-document-settings-sidebar.js @@ -8,5 +8,6 @@ export async function openDocumentSettingsSidebar() { if ( openButton ) { await openButton.click(); + await page.waitForSelector( '.edit-post-sidebar' ); } } diff --git a/packages/e2e-test-utils/src/posts.js b/packages/e2e-test-utils/src/posts.js index 16dc6fe281feaa..c0730d90c62b0b 100644 --- a/packages/e2e-test-utils/src/posts.js +++ b/packages/e2e-test-utils/src/posts.js @@ -14,14 +14,16 @@ import { visitAdminPage } from './visit-admin-page'; * Navigates to the post listing screen and bulk-trashes any posts which exist. * * @param {string} postType - String slug for type of post to trash. + * @param {string} postStatus - String status of posts to trash. * * @return {Promise} Promise resolving once posts have been trashed. */ -export async function trashAllPosts( postType = 'post' ) { +export async function trashAllPosts( postType = 'post', postStatus ) { await switchUserToAdmin(); // Visit `/wp-admin/edit.php` so we can see a list of posts and delete them. const query = addQueryArgs( '', { post_type: postType, + post_status: postStatus, } ).slice( 1 ); await visitAdminPage( 'edit.php', query ); diff --git a/packages/e2e-test-utils/src/press-key-with-modifier.js b/packages/e2e-test-utils/src/press-key-with-modifier.js index abb7f168e60901..b79f27c1d4a72b 100644 --- a/packages/e2e-test-utils/src/press-key-with-modifier.js +++ b/packages/e2e-test-utils/src/press-key-with-modifier.js @@ -81,6 +81,26 @@ async function emulateSelectAll() { } ); } +/** + * Sets the clipboard data that can be pasted with + * `pressKeyWithModifier( 'primary', 'v' )`. + * + * @param {Object} $1 Options. + * @param {string} $1.plainText Plain text to set. + * @param {string} $1.html HTML to set. + */ +export async function setClipboardData( { plainText = '', html = '' } ) { + await page.evaluate( + ( _plainText, _html ) => { + window._clipboardData = new DataTransfer(); + window._clipboardData.setData( 'text/plain', _plainText ); + window._clipboardData.setData( 'text/html', _html ); + }, + plainText, + html + ); +} + async function emulateClipboard( type ) { await page.evaluate( ( _type ) => { if ( _type !== 'paste' ) { diff --git a/packages/e2e-test-utils/src/preview.js b/packages/e2e-test-utils/src/preview.js new file mode 100644 index 00000000000000..90486bb174c65d --- /dev/null +++ b/packages/e2e-test-utils/src/preview.js @@ -0,0 +1,29 @@ +/** + * External dependencies + */ +import { last } from 'lodash'; + +/** @typedef {import('puppeteer').Page} Page */ + +/** + * Opens the preview page of an edited post. + * + * @param {Page} editorPage puppeteer editor page. + * @return {Page} preview page. + */ +export async function openPreviewPage( editorPage = page ) { + let openTabs = await browser.pages(); + const expectedTabsCount = openTabs.length + 1; + await editorPage.click( '.block-editor-post-preview__button-toggle' ); + await editorPage.waitFor( '.edit-post-header-preview__button-external' ); + await editorPage.click( '.edit-post-header-preview__button-external' ); + + // Wait for the new tab to open. + while ( openTabs.length < expectedTabsCount ) { + await editorPage.waitFor( 1 ); + openTabs = await browser.pages(); + } + + const previewPage = last( openTabs ); + return previewPage; +} diff --git a/packages/e2e-test-utils/src/shared/get-json-response.js b/packages/e2e-test-utils/src/shared/get-json-response.js index 4d8581129de799..c09d6f3a186624 100644 --- a/packages/e2e-test-utils/src/shared/get-json-response.js +++ b/packages/e2e-test-utils/src/shared/get-json-response.js @@ -6,7 +6,7 @@ */ export function getJSONResponse( obj ) { return { - content: 'application/json', + contentType: 'application/json', body: JSON.stringify( obj ), }; } diff --git a/packages/e2e-test-utils/src/switch-editor-mode-to.js b/packages/e2e-test-utils/src/switch-editor-mode-to.js index e791872cd1dc4a..8de147323a26e2 100644 --- a/packages/e2e-test-utils/src/switch-editor-mode-to.js +++ b/packages/e2e-test-utils/src/switch-editor-mode-to.js @@ -11,7 +11,7 @@ import { toggleMoreMenu } from './toggle-more-menu'; export async function switchEditorModeTo( mode ) { await toggleMoreMenu(); const [ button ] = await page.$x( - `//button[contains(text(), '${ mode } editor')]` + `//button/span[contains(text(), '${ mode } editor')]` ); await button.click( 'button' ); await toggleMoreMenu(); diff --git a/packages/e2e-test-utils/src/theme-installed.js b/packages/e2e-test-utils/src/theme-installed.js new file mode 100644 index 00000000000000..5efe882a74a271 --- /dev/null +++ b/packages/e2e-test-utils/src/theme-installed.js @@ -0,0 +1,23 @@ +/** + * Internal dependencies + */ +import { switchUserToAdmin } from './switch-user-to-admin'; +import { visitAdminPage } from './visit-admin-page'; +import { switchUserToTest } from './switch-user-to-test'; + +/** + * Checks whether a theme exists on the site. + * + * @param {string} slug Theme slug to check. + * @return {boolean} Whether the theme exists. + */ +export async function isThemeInstalled( slug ) { + await switchUserToAdmin(); + await visitAdminPage( 'themes.php' ); + + await page.waitForSelector( 'h2', { text: 'Add New Theme' } ); + const found = await page.$( `[data-slug="${ slug }"]` ); + + await switchUserToTest(); + return Boolean( found ); +} diff --git a/packages/e2e-tests/assets/1024x768_e2e_test_image_size.jpg b/packages/e2e-tests/assets/1024x768_e2e_test_image_size.jpg new file mode 100644 index 00000000000000..694e710f77c527 Binary files /dev/null and b/packages/e2e-tests/assets/1024x768_e2e_test_image_size.jpg differ diff --git a/packages/e2e-tests/assets/10x10_e2e_test_image_z9T8jK.png b/packages/e2e-tests/assets/10x10_e2e_test_image_z9T8jK.png index 4d198c0023578e..a13b8d3415a5a9 100644 Binary files a/packages/e2e-tests/assets/10x10_e2e_test_image_z9T8jK.png and b/packages/e2e-tests/assets/10x10_e2e_test_image_z9T8jK.png differ diff --git a/packages/e2e-tests/config/performance-reporter.js b/packages/e2e-tests/config/performance-reporter.js index 35ca2d8dde188c..d73d348ae4b974 100644 --- a/packages/e2e-tests/config/performance-reporter.js +++ b/packages/e2e-tests/config/performance-reporter.js @@ -28,7 +28,9 @@ class PerformanceReporter { } const results = readFileSync( filepath, 'utf8' ); - const { load, type, focus } = JSON.parse( results ); + const { load, type, focus, inserterOpen, inserterHover } = JSON.parse( + results + ); if ( load && load.length ) { // eslint-disable-next-line no-console @@ -63,6 +65,36 @@ Fastest time to select a block: ${ success( ) }` ); } + if ( inserterOpen && inserterOpen.length ) { + // eslint-disable-next-line no-console + console.log( ` +${ title( 'Opening Global Inserter Performance:' ) } +Average time to open global inserter: ${ success( + round( average( inserterOpen ) ) + 'ms' + ) } +Slowest time to open global inserter: ${ success( + round( Math.max( ...inserterOpen ) ) + 'ms' + ) } +Fastest time to open global inserter: ${ success( + round( Math.min( ...inserterOpen ) ) + 'ms' + ) }` ); + } + + if ( inserterHover && inserterHover.length ) { + // eslint-disable-next-line no-console + console.log( ` +${ title( 'Inserter Block Item Hover Performance:' ) } +Average time to move mouse between two block item in the inserter: ${ success( + round( average( inserterHover ) ) + 'ms' + ) } +Slowest time to move mouse between two block item in the inserter: ${ success( + round( Math.max( ...inserterHover ) ) + 'ms' + ) } +Fastest time to move mouse between two block item in the inserter: ${ success( + round( Math.min( ...inserterHover ) ) + 'ms' + ) }` ); + } + // eslint-disable-next-line no-console console.log( '' ); } diff --git a/packages/e2e-tests/config/setup-debug-artifacts.js b/packages/e2e-tests/config/setup-debug-artifacts.js new file mode 100644 index 00000000000000..dc491a74742280 --- /dev/null +++ b/packages/e2e-tests/config/setup-debug-artifacts.js @@ -0,0 +1,54 @@ +/** + * External dependencies + */ +const fs = require( 'fs' ); +const util = require( 'util' ); +const root = process.env.GITHUB_WORKSPACE || process.cwd(); +const ARTIFACTS_PATH = root + '/artifacts'; + +const writeFile = util.promisify( fs.writeFile ); + +if ( ! fs.existsSync( ARTIFACTS_PATH ) ) { + fs.mkdirSync( ARTIFACTS_PATH ); +} + +/** + * Gutenberg uses the default jest-jasmine2 test runner that comes with Jest. + * Unfortunately, version 2 of jasmine doesn't support async reporters. It + * does support async before and after hooks though, so the workaround here + * works by making each test wait for the artifacts before starting. + * + * Kudos to Tom Esterez (@testerez) for sharing this idea in https://github.com/smooth-code/jest-puppeteer/issues/131#issuecomment-424073620 + */ +let artifactsPromise; +// eslint-disable-next-line jest/no-jasmine-globals +jasmine.getEnv().addReporter( { + specDone: ( result ) => { + if ( result.status === 'failed' ) { + artifactsPromise = storeArtifacts( result.fullName ); + } + }, +} ); + +beforeEach( () => artifactsPromise ); +afterAll( () => artifactsPromise ); + +async function storeArtifacts( testName ) { + const slug = slugify( testName ); + await writeFile( + `${ ARTIFACTS_PATH }/${ slug }-snapshot.html`, + await page.content() + ); + await page.screenshot( { path: `${ ARTIFACTS_PATH }/${ slug }.jpg` } ); +} + +function slugify( testName ) { + const datetime = new Date().toISOString().split( '.' )[ 0 ]; + const readableName = `${ testName } ${ datetime }`; + const slug = readableName + .toLowerCase() + .replace( /:/g, '-' ) + .replace( /[^0-9a-zA-Z \-\(\)]/g, '' ) + .replace( / /g, '-' ); + return slug; +} diff --git a/packages/e2e-tests/config/setup-test-framework.js b/packages/e2e-tests/config/setup-test-framework.js index 02d7af95c0c642..eabeb0fe38da4d 100644 --- a/packages/e2e-tests/config/setup-test-framework.js +++ b/packages/e2e-tests/config/setup-test-framework.js @@ -8,6 +8,7 @@ import { get } from 'lodash'; */ import { activatePlugin, + activateTheme, clearLocalStorage, enablePageDialogAccept, isOfflineMode, @@ -201,6 +202,9 @@ async function runAxeTestsForBlockEditor() { 'link-name', 'listitem', 'region', + 'aria-required-children', + 'aria-required-parent', + 'frame-title', ], exclude: [ // Ignores elements created by metaboxes. @@ -212,6 +216,8 @@ async function runAxeTestsForBlockEditor() { // https://github.com/w3c/aria/issues/558 '[role="treegrid"] [aria-posinset]', '[role="treegrid"] [aria-setsize]', + // Ignore block previews. + '.block-editor-block-preview__content', ], } ); } @@ -257,7 +263,7 @@ beforeAll( async () => { enablePageDialogAccept(); observeConsoleLogging(); await simulateAdverseConditions(); - + await activateTheme( 'twentytwentyone' ); await trashAllPosts(); await trashAllPosts( 'wp_block' ); await setupBrowser(); diff --git a/packages/e2e-tests/fixtures/blocks/core__buttons.html b/packages/e2e-tests/fixtures/blocks/core__buttons.html index e70af7acc72ad4..c6cc0e3bf76c29 100644 --- a/packages/e2e-tests/fixtures/blocks/core__buttons.html +++ b/packages/e2e-tests/fixtures/blocks/core__buttons.html @@ -1,5 +1,5 @@ -<!-- wp:buttons --> -<div class="wp-block-buttons"> +<!-- wp:buttons {"align":"wide","contentJustification":"center"} --> +<div class="wp-block-buttons alignwide is-content-justification-center"> <!-- wp:button --> <div class="wp-block-button"><a class="wp-block-button__link">My button 1</a></div> <!-- /wp:button --> diff --git a/packages/e2e-tests/fixtures/blocks/core__buttons.json b/packages/e2e-tests/fixtures/blocks/core__buttons.json index 044daeb82101ac..0907230ef47504 100644 --- a/packages/e2e-tests/fixtures/blocks/core__buttons.json +++ b/packages/e2e-tests/fixtures/blocks/core__buttons.json @@ -3,7 +3,11 @@ "clientId": "_clientId_0", "name": "core/buttons", "isValid": true, - "attributes": {}, + "attributes": { + "contentJustification": "center", + "orientation": "horizontal", + "align": "wide" + }, "innerBlocks": [ { "clientId": "_clientId_0", @@ -26,6 +30,6 @@ "originalContent": "<div class=\"wp-block-button\"><a class=\"wp-block-button__link\">My button 2</a></div>" } ], - "originalContent": "<div class=\"wp-block-buttons\">\n\t\n\n\t\n</div>" + "originalContent": "<div class=\"wp-block-buttons alignwide is-content-justification-center\">\n\t\n\n\t\n</div>" } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__buttons.parsed.json b/packages/e2e-tests/fixtures/blocks/core__buttons.parsed.json index b96b1f50db1fc9..06f7ac796e189a 100644 --- a/packages/e2e-tests/fixtures/blocks/core__buttons.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__buttons.parsed.json @@ -1,7 +1,10 @@ [ { "blockName": "core/buttons", - "attrs": {}, + "attrs": { + "align": "wide", + "contentJustification": "center" + }, "innerBlocks": [ { "blockName": "core/button", @@ -22,9 +25,9 @@ ] } ], - "innerHTML": "\n<div class=\"wp-block-buttons\">\n\t\n\n\t\n</div>\n", + "innerHTML": "\n<div class=\"wp-block-buttons alignwide is-content-justification-center\">\n\t\n\n\t\n</div>\n", "innerContent": [ - "\n<div class=\"wp-block-buttons\">\n\t", + "\n<div class=\"wp-block-buttons alignwide is-content-justification-center\">\n\t", null, "\n\n\t", null, diff --git a/packages/e2e-tests/fixtures/blocks/core__buttons.serialized.html b/packages/e2e-tests/fixtures/blocks/core__buttons.serialized.html index baf0a0226c066c..34141befc4da2c 100644 --- a/packages/e2e-tests/fixtures/blocks/core__buttons.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__buttons.serialized.html @@ -1,5 +1,5 @@ -<!-- wp:buttons --> -<div class="wp-block-buttons"><!-- wp:button --> +<!-- wp:buttons {"contentJustification":"center","align":"wide"} --> +<div class="wp-block-buttons alignwide is-content-justification-center"><!-- wp:button --> <div class="wp-block-button"><a class="wp-block-button__link">My button 1</a></div> <!-- /wp:button --> diff --git a/packages/e2e-tests/fixtures/blocks/core__buttons__deprecated-1.html b/packages/e2e-tests/fixtures/blocks/core__buttons__deprecated-1.html new file mode 100644 index 00000000000000..29411d54215873 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__buttons__deprecated-1.html @@ -0,0 +1,11 @@ +<!-- wp:buttons {"align":"center"} --> +<div class="wp-block-buttons aligncenter"> + <!-- wp:button --> + <div class="wp-block-button"><a class="wp-block-button__link">My button 1</a></div> + <!-- /wp:button --> + + <!-- wp:button --> + <div class="wp-block-button"><a class="wp-block-button__link">My button 2</a></div> + <!-- /wp:button --> +</div> +<!-- /wp:buttons --> diff --git a/packages/e2e-tests/fixtures/blocks/core__buttons__deprecated-1.json b/packages/e2e-tests/fixtures/blocks/core__buttons__deprecated-1.json new file mode 100644 index 00000000000000..12acc3c2acca8f --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__buttons__deprecated-1.json @@ -0,0 +1,33 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/buttons", + "isValid": true, + "attributes": { + "contentJustification": "center" + }, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/button", + "isValid": true, + "attributes": { + "text": "My button 1" + }, + "innerBlocks": [], + "originalContent": "<div class=\"wp-block-button\"><a class=\"wp-block-button__link\">My button 1</a></div>" + }, + { + "clientId": "_clientId_1", + "name": "core/button", + "isValid": true, + "attributes": { + "text": "My button 2" + }, + "innerBlocks": [], + "originalContent": "<div class=\"wp-block-button\"><a class=\"wp-block-button__link\">My button 2</a></div>" + } + ], + "originalContent": "<div class=\"wp-block-buttons aligncenter\">\n\t\n\n\t\n</div>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__buttons__deprecated-1.parsed.json b/packages/e2e-tests/fixtures/blocks/core__buttons__deprecated-1.parsed.json new file mode 100644 index 00000000000000..6e9acab3132426 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__buttons__deprecated-1.parsed.json @@ -0,0 +1,45 @@ +[ + { + "blockName": "core/buttons", + "attrs": { + "align": "center" + }, + "innerBlocks": [ + { + "blockName": "core/button", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n\t<div class=\"wp-block-button\"><a class=\"wp-block-button__link\">My button 1</a></div>\n\t", + "innerContent": [ + "\n\t<div class=\"wp-block-button\"><a class=\"wp-block-button__link\">My button 1</a></div>\n\t" + ] + }, + { + "blockName": "core/button", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n\t<div class=\"wp-block-button\"><a class=\"wp-block-button__link\">My button 2</a></div>\n\t", + "innerContent": [ + "\n\t<div class=\"wp-block-button\"><a class=\"wp-block-button__link\">My button 2</a></div>\n\t" + ] + } + ], + "innerHTML": "\n<div class=\"wp-block-buttons aligncenter\">\n\t\n\n\t\n</div>\n", + "innerContent": [ + "\n<div class=\"wp-block-buttons aligncenter\">\n\t", + null, + "\n\n\t", + null, + "\n</div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__buttons__deprecated-1.serialized.html b/packages/e2e-tests/fixtures/blocks/core__buttons__deprecated-1.serialized.html new file mode 100644 index 00000000000000..b311167cbfed0e --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__buttons__deprecated-1.serialized.html @@ -0,0 +1,9 @@ +<!-- wp:buttons {"contentJustification":"center"} --> +<div class="wp-block-buttons is-content-justification-center"><!-- wp:button --> +<div class="wp-block-button"><a class="wp-block-button__link">My button 1</a></div> +<!-- /wp:button --> + +<!-- wp:button --> +<div class="wp-block-button"><a class="wp-block-button__link">My button 2</a></div> +<!-- /wp:button --></div> +<!-- /wp:buttons --> diff --git a/packages/e2e-tests/fixtures/blocks/core__embed.json b/packages/e2e-tests/fixtures/blocks/core__embed.json index b76699b65e0521..3eab30b025248b 100644 --- a/packages/e2e-tests/fixtures/blocks/core__embed.json +++ b/packages/e2e-tests/fixtures/blocks/core__embed.json @@ -1,16 +1,16 @@ [ - { - "clientId": "_clientId_0", - "name": "core/embed", - "isValid": true, - "attributes": { - "url": "https://example.com/", - "caption": "Embedded content from an example URL", - "allowResponsive": true, - "responsive": false, - "previewable": true - }, - "innerBlocks": [], - "originalContent": "<figure class=\"wp-block-embed\">\n <div class=\"wp-block-embed__wrapper\">\n https://example.com/\n </div>\n <figcaption>Embedded content from an example URL</figcaption>\n</figure>" - } + { + "clientId": "_clientId_0", + "name": "core/embed", + "isValid": true, + "attributes": { + "url": "https://example.com/", + "caption": "Embedded content from an example URL", + "allowResponsive": true, + "responsive": false, + "previewable": true + }, + "innerBlocks": [], + "originalContent": "<figure class=\"wp-block-embed\">\n <div class=\"wp-block-embed__wrapper\">\n https://example.com/\n </div>\n <figcaption>Embedded content from an example URL</figcaption>\n</figure>" + } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__latest-posts.json b/packages/e2e-tests/fixtures/blocks/core__latest-posts.json index 277eb6b63a38b5..3a2fbedad6e8f1 100644 --- a/packages/e2e-tests/fixtures/blocks/core__latest-posts.json +++ b/packages/e2e-tests/fixtures/blocks/core__latest-posts.json @@ -1,26 +1,26 @@ [ - { - "clientId": "_clientId_0", - "name": "core/latest-posts", - "isValid": true, - "attributes": { - "postsToShow": 5, - "displayPostContent": false, - "displayPostContentRadio": "excerpt", - "excerptLength": 55, - "displayAuthor": false, - "displayPostDate": false, - "postLayout": "list", - "columns": 3, - "order": "desc", - "orderBy": "date", - "displayFeaturedImage": false, - "featuredImageSizeSlug": "thumbnail", - "featuredImageSizeWidth": null, - "featuredImageSizeHeight": null, - "addLinkToFeaturedImage": false - }, - "innerBlocks": [], - "originalContent": "" - } + { + "clientId": "_clientId_0", + "name": "core/latest-posts", + "isValid": true, + "attributes": { + "postsToShow": 5, + "displayPostContent": false, + "displayPostContentRadio": "excerpt", + "excerptLength": 55, + "displayAuthor": false, + "displayPostDate": false, + "postLayout": "list", + "columns": 3, + "order": "desc", + "orderBy": "date", + "displayFeaturedImage": false, + "featuredImageSizeSlug": "thumbnail", + "featuredImageSizeWidth": null, + "featuredImageSizeHeight": null, + "addLinkToFeaturedImage": false + }, + "innerBlocks": [], + "originalContent": "" + } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.json b/packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.json index 9f9b5784eb74c2..5a0e2d328f7148 100644 --- a/packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.json +++ b/packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.json @@ -1,26 +1,26 @@ [ - { - "clientId": "_clientId_0", - "name": "core/latest-posts", - "isValid": true, - "attributes": { - "postsToShow": 5, - "displayPostContent": false, - "displayPostContentRadio": "excerpt", - "excerptLength": 55, - "displayAuthor": false, - "displayPostDate": true, - "postLayout": "list", - "columns": 3, - "order": "desc", - "orderBy": "date", - "displayFeaturedImage": false, - "featuredImageSizeSlug": "thumbnail", - "featuredImageSizeWidth": null, - "featuredImageSizeHeight": null, - "addLinkToFeaturedImage": false - }, - "innerBlocks": [], - "originalContent": "" - } + { + "clientId": "_clientId_0", + "name": "core/latest-posts", + "isValid": true, + "attributes": { + "postsToShow": 5, + "displayPostContent": false, + "displayPostContentRadio": "excerpt", + "excerptLength": 55, + "displayAuthor": false, + "displayPostDate": true, + "postLayout": "list", + "columns": 3, + "order": "desc", + "orderBy": "date", + "displayFeaturedImage": false, + "featuredImageSizeSlug": "thumbnail", + "featuredImageSizeWidth": null, + "featuredImageSizeHeight": null, + "addLinkToFeaturedImage": false + }, + "innerBlocks": [], + "originalContent": "" + } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation__deprecated.html b/packages/e2e-tests/fixtures/blocks/core__navigation__deprecated.html new file mode 100644 index 00000000000000..f64dfae11e44a7 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__navigation__deprecated.html @@ -0,0 +1,3 @@ +<!-- wp:navigation {"orientation":"horizontal","style":{"typography":{"textTransform":"var:preset|text-transform|lowercase","textDecoration":"var:preset|text-decoration|strikethrough","fontStyle":"var:preset|font-style|italic","fontWeight":"var:preset|font-weight|100"}}} --> +<!-- wp:navigation-link {"label":"WordPress","url":"https://www.wordpress.org/"} /--> +<!-- /wp:navigation --> diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation__deprecated.json b/packages/e2e-tests/fixtures/blocks/core__navigation__deprecated.json new file mode 100644 index 00000000000000..c1e7672aea7ca6 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__navigation__deprecated.json @@ -0,0 +1,34 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/navigation", + "isValid": true, + "attributes": { + "orientation": "horizontal", + "showSubmenuIcon": true, + "style": { + "typography": { + "textTransform": "lowercase", + "textDecoration": "line-through", + "fontStyle": "italic", + "fontWeight": "100" + } + } + }, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/navigation-link", + "isValid": true, + "attributes": { + "label": "WordPress", + "opensInNewTab": false, + "url": "https://www.wordpress.org/" + }, + "innerBlocks": [], + "originalContent": "" + } + ], + "originalContent": "" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation__deprecated.parsed.json b/packages/e2e-tests/fixtures/blocks/core__navigation__deprecated.parsed.json new file mode 100644 index 00000000000000..11cb1a6b198be9 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__navigation__deprecated.parsed.json @@ -0,0 +1,43 @@ +[ + { + "blockName": "core/navigation", + "attrs": { + "orientation": "horizontal", + "style": { + "typography": { + "textTransform": "var:preset|text-transform|lowercase", + "textDecoration": "var:preset|text-decoration|strikethrough", + "fontStyle": "var:preset|font-style|italic", + "fontWeight": "var:preset|font-weight|100" + } + } + }, + "innerBlocks": [ + { + "blockName": "core/navigation-link", + "attrs": { + "label": "WordPress", + "url": "https://www.wordpress.org/" + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + } + ], + "innerHTML": "\n\n", + "innerContent": [ + "\n", + null, + "\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation__deprecated.serialized.html b/packages/e2e-tests/fixtures/blocks/core__navigation__deprecated.serialized.html new file mode 100644 index 00000000000000..22bf66bf725368 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__navigation__deprecated.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:navigation {"orientation":"horizontal","style":{"typography":{"textTransform":"lowercase","textDecoration":"line-through","fontStyle":"italic","fontWeight":"100"}}} --> +<!-- wp:navigation-link {"label":"WordPress","url":"https://www.wordpress.org/"} /--> +<!-- /wp:navigation --> diff --git a/packages/e2e-tests/fixtures/blocks/core__post-featured-image.json b/packages/e2e-tests/fixtures/blocks/core__post-featured-image.json index 5ff35f8591ad0a..5ebd0b2ac8c1f8 100644 --- a/packages/e2e-tests/fixtures/blocks/core__post-featured-image.json +++ b/packages/e2e-tests/fixtures/blocks/core__post-featured-image.json @@ -1,12 +1,12 @@ [ - { - "clientId": "_clientId_0", - "name": "core/post-featured-image", - "isValid": true, - "attributes": { - "isLink": false - }, - "innerBlocks": [], - "originalContent": "" - } + { + "clientId": "_clientId_0", + "name": "core/post-featured-image", + "isValid": true, + "attributes": { + "isLink": false + }, + "innerBlocks": [], + "originalContent": "" + } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__post-title.json b/packages/e2e-tests/fixtures/blocks/core__post-title.json index 20aac7e0a3ad94..4505015af60629 100644 --- a/packages/e2e-tests/fixtures/blocks/core__post-title.json +++ b/packages/e2e-tests/fixtures/blocks/core__post-title.json @@ -1,15 +1,15 @@ [ - { - "clientId": "_clientId_0", - "name": "core/post-title", - "isValid": true, - "attributes": { - "level": 2, - "isLink": false, - "linkTarget": "_self", - "rel": "" - }, - "innerBlocks": [], - "originalContent": "" - } + { + "clientId": "_clientId_0", + "name": "core/post-title", + "isValid": true, + "attributes": { + "level": 2, + "isLink": false, + "rel": "", + "linkTarget": "_self" + }, + "innerBlocks": [], + "originalContent": "" + } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__query.json b/packages/e2e-tests/fixtures/blocks/core__query.json index e02bc6db95485a..29d1251d872b94 100644 --- a/packages/e2e-tests/fixtures/blocks/core__query.json +++ b/packages/e2e-tests/fixtures/blocks/core__query.json @@ -1,24 +1,29 @@ [ - { - "clientId": "_clientId_0", - "name": "core/query", - "isValid": true, - "attributes": { - "query": { - "perPage": null, - "pages": 1, - "offset": 0, - "postType": "post", - "categoryIds": [], - "tagIds": [], - "order": "desc", - "orderBy": "date", - "author": "", - "search": "", - "exclude": [] - } - }, - "innerBlocks": [], - "originalContent": "" - } + { + "clientId": "_clientId_0", + "name": "core/query", + "isValid": true, + "attributes": { + "query": { + "inherit": true, + "perPage": null, + "pages": 1, + "offset": 0, + "postType": "post", + "categoryIds": [], + "tagIds": [], + "order": "desc", + "orderBy": "date", + "author": "", + "search": "", + "exclude": [], + "sticky": "" + }, + "layout": { + "type": "list" + } + }, + "innerBlocks": [], + "originalContent": "" + } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__search.json b/packages/e2e-tests/fixtures/blocks/core__search.json index 9bfe776f2127f4..26c1bad1c57f44 100644 --- a/packages/e2e-tests/fixtures/blocks/core__search.json +++ b/packages/e2e-tests/fixtures/blocks/core__search.json @@ -4,10 +4,10 @@ "name": "core/search", "isValid": true, "attributes": { - "buttonPosition": "button-outside", - "buttonUseIcon": false, + "showLabel": true, "placeholder": "", - "showLabel": true + "buttonPosition": "button-outside", + "buttonUseIcon": false }, "innerBlocks": [], "originalContent": "" diff --git a/packages/e2e-tests/fixtures/blocks/core__search__custom-text.json b/packages/e2e-tests/fixtures/blocks/core__search__custom-text.json index 5d2c86a18d5907..a490ed43df73f2 100644 --- a/packages/e2e-tests/fixtures/blocks/core__search__custom-text.json +++ b/packages/e2e-tests/fixtures/blocks/core__search__custom-text.json @@ -4,12 +4,12 @@ "name": "core/search", "isValid": true, "attributes": { - "buttonPosition": "button-outside", "label": "Custom label", + "showLabel": true, "placeholder": "Custom placeholder", "buttonText": "Custom button text", - "buttonUseIcon": false, - "showLabel": true + "buttonPosition": "button-outside", + "buttonUseIcon": false }, "innerBlocks": [], "originalContent": "" diff --git a/packages/e2e-tests/fixtures/blocks/core__social-links.json b/packages/e2e-tests/fixtures/blocks/core__social-links.json index 42232b6887ae56..eaf67458e69383 100644 --- a/packages/e2e-tests/fixtures/blocks/core__social-links.json +++ b/packages/e2e-tests/fixtures/blocks/core__social-links.json @@ -4,8 +4,8 @@ "name": "core/social-links", "isValid": true, "attributes": { - "openInNewTab": false - }, + "openInNewTab": false + }, "innerBlocks": [ { "clientId": "_clientId_0", diff --git a/packages/e2e-tests/fixtures/blocks/core__video.json b/packages/e2e-tests/fixtures/blocks/core__video.json index d3c9d1ddbd380f..8b5963a98c714a 100644 --- a/packages/e2e-tests/fixtures/blocks/core__video.json +++ b/packages/e2e-tests/fixtures/blocks/core__video.json @@ -11,8 +11,8 @@ "muted": false, "preload": "metadata", "src": "data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=", - "playsInline": false, - "tracks": [] + "playsInline": false, + "tracks": [] }, "innerBlocks": [], "originalContent": "<figure class=\"wp-block-video\"><video controls src=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"></video></figure>" diff --git a/packages/e2e-tests/fixtures/blocks/core_buttons__simple__deprecated.json b/packages/e2e-tests/fixtures/blocks/core_buttons__simple__deprecated.json index d77d9752de37b0..1ae83f325b61d5 100644 --- a/packages/e2e-tests/fixtures/blocks/core_buttons__simple__deprecated.json +++ b/packages/e2e-tests/fixtures/blocks/core_buttons__simple__deprecated.json @@ -3,7 +3,9 @@ "clientId": "_clientId_0", "name": "core/buttons", "isValid": true, - "attributes": {}, + "attributes": { + "orientation": "horizontal" + }, "innerBlocks": [ { "clientId": "_clientId_0", diff --git a/packages/e2e-tests/jest.config.js b/packages/e2e-tests/jest.config.js index 4d2add7ca85427..eeceeb7cc470be 100644 --- a/packages/e2e-tests/jest.config.js +++ b/packages/e2e-tests/jest.config.js @@ -2,6 +2,7 @@ module.exports = { ...require( '@wordpress/scripts/config/jest-e2e.config' ), setupFiles: [ '<rootDir>/config/gutenberg-phase.js' ], setupFilesAfterEnv: [ + '<rootDir>/config/setup-debug-artifacts.js', '<rootDir>/config/setup-test-framework.js', '@wordpress/jest-console', '@wordpress/jest-puppeteer-axe', diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index cfd91eac49cc21..7e555cedd89b85 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-tests", - "version": "1.24.4", + "version": "1.24.5", "description": "End-To-End (E2E) tests for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,7 +31,7 @@ "chalk": "^4.0.0", "expect-puppeteer": "^4.4.0", "lodash": "^4.17.19", - "uuid": "^7.0.2" + "uuid": "^8.3.0" }, "peerDependencies": { "jest": ">=24", diff --git a/packages/e2e-tests/plugins/block-context/index.js b/packages/e2e-tests/plugins/block-context/index.js index 0bca6a508074e7..cfc233a927ac77 100644 --- a/packages/e2e-tests/plugins/block-context/index.js +++ b/packages/e2e-tests/plugins/block-context/index.js @@ -32,6 +32,7 @@ el( InnerBlocks, { template: [ [ 'gutenberg/test-context-consumer', {} ] ], templateLock: 'all', + templateInsertUpdatesSelection: true, } ) ); }, diff --git a/packages/e2e-tests/plugins/image-size.php b/packages/e2e-tests/plugins/image-size.php new file mode 100644 index 00000000000000..55cdd022431507 --- /dev/null +++ b/packages/e2e-tests/plugins/image-size.php @@ -0,0 +1,32 @@ +<?php +/** + * Plugin Name: Gutenberg Test Image Size + * Plugin URI: https://github.com/WordPress/gutenberg + * Author: Gutenberg Team + * + * @package gutenberg-test-image-size + */ + +/** + * Registers a custom script for the plugin. + */ +function gutenberg_test_create_image_size() { + if ( function_exists( 'add_image_size' ) ) { + add_image_size( 'custom-size-one', 499 ); + } +} + +/** + * Add custom size + * + * @param Array $sizes Size name. + */ +function gutenberg_test_create_image_size_name( $sizes ) { + $custom_sizes = array( + 'custom-size-one' => 'Custom Size One', + ); + return array_merge( $sizes, $custom_sizes ); +} + +add_action( 'after_setup_theme', 'gutenberg_test_create_image_size' ); +add_filter( 'image_size_names_choose', 'gutenberg_test_create_image_size_name' ); diff --git a/packages/e2e-tests/plugins/inner-blocks-templates/index.js b/packages/e2e-tests/plugins/inner-blocks-templates/index.js index e6897e4b7d9500..8658bac1b47027 100644 --- a/packages/e2e-tests/plugins/inner-blocks-templates/index.js +++ b/packages/e2e-tests/plugins/inner-blocks-templates/index.js @@ -1,4 +1,4 @@ -( function() { +( function () { var registerBlockType = wp.blocks.registerBlockType; var createBlock = wp.blocks.createBlock; var el = wp.element.createElement; @@ -24,7 +24,7 @@ ], ]; - var save = function() { + var save = function () { return el( InnerBlocks.Content ); }; @@ -33,7 +33,7 @@ icon: 'cart', category: 'text', - edit: function( props ) { + edit: function ( props ) { return el( InnerBlocks, { template: TEMPLATE, } ); @@ -47,7 +47,7 @@ icon: 'cart', category: 'text', - edit: function( props ) { + edit: function ( props ) { return el( InnerBlocks, { template: TEMPLATE, templateLock: 'all', @@ -62,9 +62,10 @@ icon: 'cart', category: 'text', - edit: function( props ) { + edit: function ( props ) { return el( InnerBlocks, { template: TEMPLATE_PARAGRAPH_PLACEHOLDER, + templateInsertUpdatesSelection: true, } ); }, @@ -86,7 +87,7 @@ 'test/test-inner-blocks-locking-all', 'test/test-inner-blocks-paragraph-placeholder', ], - transform: function( attributes, innerBlocks ) { + transform: function ( attributes, innerBlocks ) { return createBlock( 'test/test-inner-blocks-transformer-target', attributes, @@ -99,7 +100,7 @@ { type: 'block', blocks: [ 'test/i-dont-exist' ], - transform: function( attributes, innerBlocks ) { + transform: function ( attributes, innerBlocks ) { return createBlock( 'test/test-inner-blocks-transformer-target', attributes, @@ -110,7 +111,7 @@ ], }, - edit: function( props ) { + edit: function ( props ) { return el( InnerBlocks, { template: TEMPLATE, } ); diff --git a/packages/e2e-tests/specs/editor/blocks/__snapshots__/code.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/code.test.js.snap index 50ee83d373c65e..3b30c08206ba0d 100644 --- a/packages/e2e-tests/specs/editor/blocks/__snapshots__/code.test.js.snap +++ b/packages/e2e-tests/specs/editor/blocks/__snapshots__/code.test.js.snap @@ -5,3 +5,10 @@ exports[`Code can be created by three backticks and enter 1`] = ` <pre class=\\"wp-block-code\\"><code>&lt;?php</code></pre> <!-- /wp:code -->" `; + +exports[`Code should paste plain text 1`] = ` +"<!-- wp:code --> +<pre class=\\"wp-block-code\\"><code>&lt;img /> + &lt;br></code></pre> +<!-- /wp:code -->" +`; diff --git a/packages/e2e-tests/specs/editor/blocks/__snapshots__/image.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/image.test.js.snap new file mode 100644 index 00000000000000..18b856a57a4303 --- /dev/null +++ b/packages/e2e-tests/specs/editor/blocks/__snapshots__/image.test.js.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Image allows changing aspect ratio using the crop tools 1`] = `"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAACQf/xAAgEAAAAwkBAAAAAAAAAAAAAAAABAUBAwYJNTh0d7K1/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AJTJUuninX570U4K8rVU7kPOmgoZKl08U6/PeinBXlaqnch500B//9k="`; + +exports[`Image allows changing aspect ratio using the crop tools 2`] = `"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAGAAoDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAcJ/8QAIBAAAQIFBQAAAAAAAAAAAAAAAAQFAQMGCTU4dHeytf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCU2VNU9U8frvRbjV52yq3cTO0QAP/Z"`; + +exports[`Image allows rotating using the crop tools 1`] = `"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAACQf/xAAgEAAAAwkBAAAAAAAAAAAAAAAABAUBAwYJNTh0d7K1/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AJTJUuninX570U4K8rVU7kPOmgoZKl08U6/PeinBXlaqnch500B//9k="`; + +exports[`Image allows rotating using the crop tools 2`] = `"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAcJ/8QAIRAAAAILAQAAAAAAAAAAAAAAAAUCBAcIFRlVVpOV0dT/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AlMlR6e/mVbUx8ISVHp7+ZVtTHwjV6LGtTW8yXQixrU1vMl0B/9k="`; + +exports[`Image allows zooming using the crop tools 1`] = `"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAACQf/xAAgEAAAAwkBAAAAAAAAAAAAAAAABAUBAwYJNTh0d7K1/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AJTJUuninX570U4K8rVU7kPOmgoZKl08U6/PeinBXlaqnch500B//9k="`; + +exports[`Image allows zooming using the crop tools 2`] = `"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAFAAUDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAj/xAAaEAEAAQUAAAAAAAAAAAAAAAAABAcJOIW1/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKptcYJ0y3XYmgA//9k="`; + +exports[`Image should drag and drop files into media placeholder 1`] = ` +"<!-- wp:image --> +<figure class=\\"wp-block-image\\"><img alt=\\"\\"/></figure> +<!-- /wp:image -->" +`; diff --git a/packages/e2e-tests/specs/editor/blocks/__snapshots__/list.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/list.test.js.snap index 9a14e06f52d8b0..c2421bc8c80e2f 100644 --- a/packages/e2e-tests/specs/editor/blocks/__snapshots__/list.test.js.snap +++ b/packages/e2e-tests/specs/editor/blocks/__snapshots__/list.test.js.snap @@ -308,19 +308,19 @@ exports[`List should undo asterisk transform with backspace 1`] = ` <!-- /wp:paragraph -->" `; -exports[`List should undo asterisk transform with backspace after mouse move 1`] = ` +exports[`List should undo asterisk transform with backspace after selection changes 1`] = ` "<!-- wp:paragraph --> <p>* </p> <!-- /wp:paragraph -->" `; -exports[`List should undo asterisk transform with backspace after selection changes 1`] = ` +exports[`List should undo asterisk transform with backspace after selection changes without requestIdleCallback 1`] = ` "<!-- wp:paragraph --> <p>* </p> <!-- /wp:paragraph -->" `; -exports[`List should undo asterisk transform with backspace after selection changes without requestIdleCallback 1`] = ` +exports[`List should undo asterisk transform with backspace setting isTyping state 1`] = ` "<!-- wp:paragraph --> <p>* </p> <!-- /wp:paragraph -->" diff --git a/packages/e2e-tests/specs/editor/blocks/buttons.test.js b/packages/e2e-tests/specs/editor/blocks/buttons.test.js index 3938bc2edb6c1d..9253d29b1cbd41 100644 --- a/packages/e2e-tests/specs/editor/blocks/buttons.test.js +++ b/packages/e2e-tests/specs/editor/blocks/buttons.test.js @@ -24,7 +24,15 @@ describe( 'Buttons', () => { // Regression: https://github.com/WordPress/gutenberg/pull/19885 await insertBlock( 'Buttons' ); await pressKeyWithModifier( 'primary', 'k' ); + await page.waitForFunction( + () => !! document.activeElement.closest( '.block-editor-url-input' ) + ); await page.keyboard.press( 'Escape' ); + await page.waitForFunction( + () => + document.activeElement === + document.querySelector( '.block-editor-rich-text__editable' ) + ); await page.keyboard.type( 'WordPress' ); expect( await getEditedPostContent() ).toMatchSnapshot(); diff --git a/packages/e2e-tests/specs/editor/blocks/code.test.js b/packages/e2e-tests/specs/editor/blocks/code.test.js index 018ce71f9794da..3747afec5e632d 100644 --- a/packages/e2e-tests/specs/editor/blocks/code.test.js +++ b/packages/e2e-tests/specs/editor/blocks/code.test.js @@ -2,9 +2,12 @@ * WordPress dependencies */ import { + insertBlock, clickBlockAppender, getEditedPostContent, createNewPost, + setClipboardData, + pressKeyWithModifier, } from '@wordpress/e2e-test-utils'; describe( 'Code', () => { @@ -20,4 +23,26 @@ describe( 'Code', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should delete block when backspace in an empty code', async () => { + await insertBlock( 'Code' ); + await page.keyboard.type( 'a' ); + + await page.keyboard.press( 'Backspace' ); + await page.keyboard.press( 'Backspace' ); + + // Expect code block to be deleted. + expect( await getEditedPostContent() ).toBe( '' ); + } ); + + it( 'should paste plain text', async () => { + await insertBlock( 'Code' ); + + // Test to see if HTML and white space is kept. + await setClipboardData( { plainText: '<img />\n\t<br>' } ); + + await pressKeyWithModifier( 'primary', 'v' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/e2e-tests/specs/editor/blocks/image.test.js b/packages/e2e-tests/specs/editor/blocks/image.test.js index 5e6cd85099385a..b254413a141df9 100644 --- a/packages/e2e-tests/specs/editor/blocks/image.test.js +++ b/packages/e2e-tests/specs/editor/blocks/image.test.js @@ -14,7 +14,10 @@ import { getEditedPostContent, createNewPost, clickButton, + clickBlockToolbarButton, + clickMenuItem, openDocumentSettingsSidebar, + pressKeyWithModifier, } from '@wordpress/e2e-test-utils'; async function upload( selector ) { @@ -32,10 +35,27 @@ async function upload( selector ) { const tmpFileName = path.join( os.tmpdir(), filename + '.png' ); fs.copyFileSync( testImagePath, tmpFileName ); await inputElement.uploadFile( tmpFileName ); + return filename; +} + +async function waitForImage( filename ) { await page.waitForSelector( `.wp-block-image img[src$="${ filename }.png"]` ); - return filename; +} + +async function getSrc( elementHandle ) { + return elementHandle.evaluate( ( node ) => node.src ); +} +async function getDataURL( elementHandle ) { + return elementHandle.evaluate( ( node ) => { + const canvas = document.createElement( 'canvas' ); + const context = canvas.getContext( '2d' ); + canvas.width = node.width; + canvas.height = node.height; + context.drawImage( node, 0, 0 ); + return canvas.toDataURL( 'image/jpeg' ); + } ); } describe( 'Image', () => { @@ -46,6 +66,7 @@ describe( 'Image', () => { it( 'can be inserted', async () => { await insertBlock( 'Image' ); const filename = await upload( '.wp-block-image input[type="file"]' ); + await waitForImage( filename ); const regex = new RegExp( `<!-- wp:image {"id":\\d+,"sizeSlug":"large","linkDestination":"none"} -->\\s*<figure class="wp-block-image size-large"><img src="[^"]+\\/${ filename }\\.png" alt="" class="wp-image-\\d+"/></figure>\\s*<!-- \\/wp:image -->` @@ -56,6 +77,7 @@ describe( 'Image', () => { it( 'should replace, reset size, and keep selection', async () => { await insertBlock( 'Image' ); const filename1 = await upload( '.wp-block-image input[type="file"]' ); + await waitForImage( filename1 ); const regex1 = new RegExp( `<!-- wp:image {"id":\\d+,"sizeSlug":"large","linkDestination":"none"} -->\\s*<figure class="wp-block-image size-large"><img src="[^"]+\\/${ filename1 }\\.png" alt="" class="wp-image-\\d+"/></figure>\\s*<!-- \\/wp:image -->` @@ -75,6 +97,7 @@ describe( 'Image', () => { const filename2 = await upload( '.block-editor-media-replace-flow__options input[type="file"]' ); + await waitForImage( filename2 ); const regex3 = new RegExp( `<!-- wp:image {"id":\\d+,"sizeSlug":"large","linkDestination":"none"} -->\\s*<figure class="wp-block-image size-large"><img src="[^"]+\\/${ filename2 }\\.png" alt="" class="wp-image-\\d+"/></figure>\\s*<!-- \\/wp:image -->` @@ -89,7 +112,8 @@ describe( 'Image', () => { it( 'should place caret at end of caption after merging empty paragraph', async () => { await insertBlock( 'Image' ); - await upload( '.wp-block-image input[type="file"]' ); + const fileName = await upload( '.wp-block-image input[type="file"]' ); + await waitForImage( fileName ); await page.keyboard.type( '1' ); await insertBlock( 'Paragraph' ); await page.keyboard.press( 'Backspace' ); @@ -102,7 +126,8 @@ describe( 'Image', () => { it( 'should allow soft line breaks in caption', async () => { await insertBlock( 'Image' ); - await upload( '.wp-block-image input[type="file"]' ); + const fileName = await upload( '.wp-block-image input[type="file"]' ); + await waitForImage( fileName ); await page.keyboard.type( '12' ); await page.keyboard.press( 'ArrowLeft' ); await page.keyboard.press( 'Enter' ); @@ -111,4 +136,156 @@ describe( 'Image', () => { await page.evaluate( () => document.activeElement.innerHTML ) ).toBe( '1<br data-rich-text-line-break="true">2' ); } ); + + it( 'should drag and drop files into media placeholder', async () => { + await page.keyboard.press( 'Enter' ); + await insertBlock( 'Image' ); + + // Confirm correct setup. + expect( await getEditedPostContent() ).toMatchSnapshot(); + + const image = await page.$( '[data-type="core/image"]' ); + + await image.evaluate( () => { + const input = document.createElement( 'input' ); + input.type = 'file'; + input.id = 'wp-temp-test-input'; + document.body.appendChild( input ); + } ); + + const fileName = await upload( '#wp-temp-test-input' ); + + const paragraphRect = await image.boundingBox(); + const pX = paragraphRect.x + paragraphRect.width / 2; + const pY = paragraphRect.y + paragraphRect.height / 3; + + await image.evaluate( + ( element, clientX, clientY ) => { + const input = document.getElementById( 'wp-temp-test-input' ); + const dataTransfer = new DataTransfer(); + dataTransfer.items.add( input.files[ 0 ] ); + const event = new DragEvent( 'drop', { + bubbles: true, + clientX, + clientY, + dataTransfer, + } ); + element.dispatchEvent( event ); + }, + pX, + pY + ); + + await waitForImage( fileName ); + } ); + + it( 'allows zooming using the crop tools', async () => { + // Insert the block, upload a file and crop. + await insertBlock( 'Image' ); + const filename = await upload( '.wp-block-image input[type="file"]' ); + await waitForImage( filename ); + + // Assert that the image is initially unscaled and unedited. + const initialImage = await page.$( '.wp-block-image img' ); + const initialImageSrc = await getSrc( initialImage ); + const initialImageDataURL = await getDataURL( initialImage ); + expect( initialImageDataURL ).toMatchSnapshot(); + + // Zoom in to twice the amount using the zoom input. + await clickBlockToolbarButton( 'Crop' ); + await clickBlockToolbarButton( 'Zoom' ); + await page.waitForFunction( () => + document.activeElement.classList.contains( + 'components-range-control__slider' + ) + ); + await page.keyboard.press( 'Tab' ); + await page.waitForFunction( () => + document.activeElement.classList.contains( + 'components-input-control__input' + ) + ); + await pressKeyWithModifier( 'primary', 'a' ); + await page.keyboard.type( '200' ); + await page.keyboard.press( 'Escape' ); + await clickBlockToolbarButton( 'Apply', 'content' ); + + // Wait for the cropping tools to disappear. + await page.waitForSelector( + '.wp-block-image img:not( .reactEasyCrop_Image )' + ); + + // Assert that the image is edited. + const updatedImage = await page.$( '.wp-block-image img' ); + const updatedImageSrc = await getSrc( updatedImage ); + expect( initialImageSrc ).not.toEqual( updatedImageSrc ); + const updatedImageDataURL = await getDataURL( updatedImage ); + expect( initialImageDataURL ).not.toEqual( updatedImageDataURL ); + expect( updatedImageDataURL ).toMatchSnapshot(); + } ); + + it( 'allows changing aspect ratio using the crop tools', async () => { + // Insert the block, upload a file and crop. + await insertBlock( 'Image' ); + const filename = await upload( '.wp-block-image input[type="file"]' ); + await waitForImage( filename ); + + // Assert that the image is initially unscaled and unedited. + const initialImage = await page.$( '.wp-block-image img' ); + const initialImageSrc = await getSrc( initialImage ); + const initialImageDataURL = await getDataURL( initialImage ); + expect( initialImageDataURL ).toMatchSnapshot(); + + // Zoom in to twice the amount using the zoom input. + await clickBlockToolbarButton( 'Crop' ); + await clickBlockToolbarButton( 'Aspect Ratio' ); + await page.waitForFunction( () => + document.activeElement.classList.contains( + 'components-menu-item__button' + ) + ); + await clickMenuItem( '16:10' ); + await clickBlockToolbarButton( 'Apply', 'content' ); + + // Wait for the cropping tools to disappear. + await page.waitForSelector( + '.wp-block-image img:not( .reactEasyCrop_Image )' + ); + + // Assert that the image is edited. + const updatedImage = await page.$( '.wp-block-image img' ); + const updatedImageSrc = await getSrc( updatedImage ); + expect( initialImageSrc ).not.toEqual( updatedImageSrc ); + const updatedImageDataURL = await getDataURL( updatedImage ); + expect( initialImageDataURL ).not.toEqual( updatedImageDataURL ); + expect( updatedImageDataURL ).toMatchSnapshot(); + } ); + + it( 'allows rotating using the crop tools', async () => { + // Insert the block, upload a file and crop. + await insertBlock( 'Image' ); + const filename = await upload( '.wp-block-image input[type="file"]' ); + await waitForImage( filename ); + + // Assert that the image is initially unscaled and unedited. + const initialImage = await page.$( '.wp-block-image img' ); + const initialImageDataURL = await getDataURL( initialImage ); + expect( initialImageDataURL ).toMatchSnapshot(); + + // Double the image's size using the zoom input. + await clickBlockToolbarButton( 'Crop' ); + await page.waitForSelector( '.wp-block-image img.reactEasyCrop_Image' ); + await clickBlockToolbarButton( 'Rotate' ); + await clickBlockToolbarButton( 'Apply', 'content' ); + + await page.waitForSelector( + '.wp-block-image img:not( .reactEasyCrop_Image )' + ); + + // Assert that the image is edited. + const updatedImage = await page.$( '.wp-block-image img' ); + const updatedImageDataURL = await getDataURL( updatedImage ); + expect( initialImageDataURL ).not.toEqual( updatedImageDataURL ); + expect( updatedImageDataURL ).toMatchSnapshot(); + } ); } ); diff --git a/packages/e2e-tests/specs/editor/blocks/list.test.js b/packages/e2e-tests/specs/editor/blocks/list.test.js index 0289fddbcee573..bba09376edfa69 100644 --- a/packages/e2e-tests/specs/editor/blocks/list.test.js +++ b/packages/e2e-tests/specs/editor/blocks/list.test.js @@ -10,6 +10,7 @@ import { transformBlockTo, pressKeyWithModifier, insertBlock, + showBlockToolbar, } from '@wordpress/e2e-test-utils'; describe( 'List', () => { @@ -72,10 +73,10 @@ describe( 'List', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); - it( 'should undo asterisk transform with backspace after mouse move', async () => { + it( 'should undo asterisk transform with backspace setting isTyping state', async () => { await clickBlockAppender(); await page.keyboard.type( '* ' ); - await page.mouse.move( 0, 0, { steps: 10 } ); + await showBlockToolbar(); await page.keyboard.press( 'Backspace' ); expect( await getEditedPostContent() ).toMatchSnapshot(); diff --git a/packages/e2e-tests/specs/editor/blocks/preformatted.test.js b/packages/e2e-tests/specs/editor/blocks/preformatted.test.js index 491d1bdbccb42a..83e6e3383b6cf3 100644 --- a/packages/e2e-tests/specs/editor/blocks/preformatted.test.js +++ b/packages/e2e-tests/specs/editor/blocks/preformatted.test.js @@ -3,10 +3,10 @@ */ import { clickBlockToolbarButton, + clickMenuItem, getEditedPostContent, createNewPost, insertBlock, - clickButton, } from '@wordpress/e2e-test-utils'; describe( 'Preformatted', () => { @@ -23,12 +23,12 @@ describe( 'Preformatted', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); await clickBlockToolbarButton( 'More options' ); - await clickButton( 'Convert to Blocks' ); + await clickMenuItem( 'Convert to Blocks' ); // Once it's edited, it should be saved as BR tags. await page.keyboard.type( '0' ); await page.keyboard.press( 'Enter' ); await clickBlockToolbarButton( 'More options' ); - await clickButton( 'Edit as HTML' ); + await clickMenuItem( 'Edit as HTML' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -46,4 +46,16 @@ describe( 'Preformatted', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should delete block when backspace in an empty preformatted', async () => { + await insertBlock( 'Preformatted' ); + + await page.keyboard.type( 'a' ); + + await page.keyboard.press( 'Backspace' ); + await page.keyboard.press( 'Backspace' ); + + // Expect preformatted block to be deleted. + expect( await getEditedPostContent() ).toBe( '' ); + } ); } ); diff --git a/packages/e2e-tests/specs/editor/plugins/allowed-blocks.test.js b/packages/e2e-tests/specs/editor/plugins/allowed-blocks.test.js index 5a638574a0fc01..ba98a92319d074 100644 --- a/packages/e2e-tests/specs/editor/plugins/allowed-blocks.test.js +++ b/packages/e2e-tests/specs/editor/plugins/allowed-blocks.test.js @@ -29,7 +29,6 @@ describe( 'Allowed Blocks Filter', () => { await page.$x( `//button//span[contains(text(), 'Paragraph')]` ) )[ 0 ]; expect( paragraphBlockButton ).not.toBeNull(); - await paragraphBlockButton.click(); // The gallery block is not available. await searchForBlock( 'Gallery' ); diff --git a/packages/e2e-tests/specs/editor/plugins/annotations.test.js b/packages/e2e-tests/specs/editor/plugins/annotations.test.js index a14fa73ae8cd91..14cfbcaa1e7c41 100644 --- a/packages/e2e-tests/specs/editor/plugins/annotations.test.js +++ b/packages/e2e-tests/specs/editor/plugins/annotations.test.js @@ -4,6 +4,7 @@ import { activatePlugin, clickBlockToolbarButton, + clickMenuItem, clickOnMoreMenuItem, createNewPost, deactivatePlugin, @@ -11,12 +12,7 @@ import { const clickOnBlockSettingsMenuItem = async ( buttonLabel ) => { await clickBlockToolbarButton( 'More options' ); - const itemButton = ( - await page.$x( - `//*[contains(@class, "block-editor-block-settings-menu__popover")]//button[contains(text(), '${ buttonLabel }')]` - ) - )[ 0 ]; - await itemButton.click(); + await clickMenuItem( buttonLabel ); }; const ANNOTATIONS_SELECTOR = '.annotation-text-e2e-tests'; diff --git a/packages/e2e-tests/specs/editor/plugins/block-context.test.js b/packages/e2e-tests/specs/editor/plugins/block-context.test.js index 3a96eb3e141f6a..f09df529e70ce0 100644 --- a/packages/e2e-tests/specs/editor/plugins/block-context.test.js +++ b/packages/e2e-tests/specs/editor/plugins/block-context.test.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { last } from 'lodash'; - /** * WordPress dependencies */ @@ -12,29 +7,9 @@ import { deactivatePlugin, insertBlock, saveDraft, + openPreviewPage, } from '@wordpress/e2e-test-utils'; -async function openPreviewPage( editorPage ) { - let openTabs = await browser.pages(); - const expectedTabsCount = openTabs.length + 1; - await editorPage.click( '.block-editor-post-preview__button-toggle' ); - await editorPage.waitFor( '.edit-post-header-preview__button-external' ); - await editorPage.click( '.edit-post-header-preview__button-external' ); - - // Wait for the new tab to open. - while ( openTabs.length < expectedTabsCount ) { - await editorPage.waitFor( 1 ); - openTabs = await browser.pages(); - } - - const previewPage = last( openTabs ); - // Wait for the preview to load. We can't do interstitial detection here, - // because it might load too quickly for us to pick up, so we wait for - // the preview to load by waiting for the content to appear. - await previewPage.waitForSelector( '.entry-content' ); - return previewPage; -} - describe( 'Block context', () => { beforeAll( async () => { await activatePlugin( 'gutenberg-test-block-context' ); @@ -74,6 +49,7 @@ describe( 'Block context', () => { await insertBlock( 'Test Context Provider' ); const editorPage = page; const previewPage = await openPreviewPage( editorPage ); + await previewPage.waitForSelector( '.entry-content' ); // Check default context values are populated. let content = await previewPage.$eval( diff --git a/packages/e2e-tests/specs/editor/plugins/block-directory-add.test.js b/packages/e2e-tests/specs/editor/plugins/block-directory-add.test.js index 39c35982db5281..dde952f6274774 100644 --- a/packages/e2e-tests/specs/editor/plugins/block-directory-add.test.js +++ b/packages/e2e-tests/specs/editor/plugins/block-directory-add.test.js @@ -168,7 +168,7 @@ describe( 'adding blocks from block directory', () => { const selectorContent = await page.evaluate( () => - document.querySelector( '.block-editor-inserter__block-list' ) + document.querySelector( '.block-editor-inserter__main-area' ) .innerHTML ); expect( selectorContent ).toContain( 'has-no-results' ); diff --git a/packages/e2e-tests/specs/editor/plugins/block-icons.test.js b/packages/e2e-tests/specs/editor/plugins/block-icons.test.js index fc2335b437c9fe..d8fc23bf64e860 100644 --- a/packages/e2e-tests/specs/editor/plugins/block-icons.test.js +++ b/packages/e2e-tests/specs/editor/plugins/block-icons.test.js @@ -12,7 +12,7 @@ import { } from '@wordpress/e2e-test-utils'; const INSERTER_BUTTON_SELECTOR = - '.block-editor-inserter__block-list .block-editor-block-types-list__item'; + '.block-editor-inserter__main-area .block-editor-block-types-list__item'; const INSERTER_ICON_WRAPPER_SELECTOR = `${ INSERTER_BUTTON_SELECTOR } .block-editor-block-types-list__item-icon`; const INSERTER_ICON_SELECTOR = `${ INSERTER_BUTTON_SELECTOR } .block-editor-block-icon`; const INSPECTOR_ICON_SELECTOR = '.edit-post-sidebar .block-editor-block-icon'; diff --git a/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js b/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js index 59bb45283fca58..1eefa0a69ebbf4 100644 --- a/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js +++ b/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js @@ -4,6 +4,7 @@ import { activatePlugin, clickBlockToolbarButton, + clickMenuItem, createNewPost, deactivatePlugin, getEditedPostContent, @@ -40,7 +41,7 @@ describe( 'cpt locking', () => { ); await clickBlockToolbarButton( 'More options' ); expect( - await page.$x( '//button[contains(text(), "Remove block")]' ) + await page.$x( '//button/span[contains(text(), "Remove block")]' ) ).toHaveLength( 0 ); }; @@ -171,10 +172,7 @@ describe( 'cpt locking', () => { 'p1' ); await clickBlockToolbarButton( 'More options' ); - const [ removeBlock ] = await page.$x( - '//button[contains(text(), "Remove block")]' - ); - await removeBlock.click(); + await clickMenuItem( 'Remove block' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -194,10 +192,8 @@ describe( 'cpt locking', () => { 'p1' ); await clickBlockToolbarButton( 'More options' ); - const [ removeBlock ] = await page.$x( - '//button[contains(text(), "Remove block")]' - ); - await removeBlock.click(); + await clickMenuItem( 'Remove block' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); } ); diff --git a/packages/e2e-tests/specs/editor/plugins/image-size.test.js b/packages/e2e-tests/specs/editor/plugins/image-size.test.js new file mode 100644 index 00000000000000..a92ae630465506 --- /dev/null +++ b/packages/e2e-tests/specs/editor/plugins/image-size.test.js @@ -0,0 +1,79 @@ +/** + * External dependencies + */ +import path from 'path'; +import fs from 'fs'; +import os from 'os'; +import { v4 as uuid } from 'uuid'; + +/** + * WordPress dependencies + */ +import { + activatePlugin, + createNewPost, + clickButton, + deactivatePlugin, + insertBlock, + openDocumentSettingsSidebar, +} from '@wordpress/e2e-test-utils'; + +describe( 'changing image size', () => { + beforeEach( async () => { + await activatePlugin( 'gutenberg-test-image-size' ); + await createNewPost(); + } ); + + afterEach( async () => { + await deactivatePlugin( 'gutenberg-test-image-size' ); + } ); + + it( 'should insert and change my image size', async () => { + await insertBlock( 'Image' ); + await clickButton( 'Media Library' ); + + // Wait for media modal to appear and upload image. + await page.waitForSelector( '.media-modal input[type=file]' ); + const inputElement = await page.$( '.media-modal input[type=file]' ); + const testImagePath = path.join( + __dirname, + '..', + '..', + '..', + 'assets', + '1024x768_e2e_test_image_size.jpg' + ); + const filename = uuid(); + const tmpFileName = path.join( os.tmpdir(), filename + '.jpg' ); + fs.copyFileSync( testImagePath, tmpFileName ); + await inputElement.uploadFile( tmpFileName ); + + // Wait for upload to finish. + await page.waitForSelector( + `.media-modal li[aria-label="${ filename }"]` + ); + + // Insert the uploaded image. + await page.click( '.media-modal button.media-button-select' ); + + // Select the new size updated with the plugin. + await openDocumentSettingsSidebar(); + const imageSizeLabel = await page.waitForXPath( + '//label[text()="Image size"]' + ); + await imageSizeLabel.click(); + const imageSizeSelect = await page.evaluateHandle( + () => document.activeElement + ); + await imageSizeSelect.select( 'custom-size-one' ); + + // Verify that the custom size was applied to the image. + await page.waitForSelector( '.wp-block-image.size-custom-size-one' ); + await page.waitForFunction( + () => + document.querySelector( + '.block-editor-image-size-control__width input' + ).value === '499' + ); + } ); +} ); diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/adding-patterns.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/adding-patterns.test.js.snap index bf3dee614c9c76..d3894f3030d376 100644 --- a/packages/e2e-tests/specs/editor/various/__snapshots__/adding-patterns.test.js.snap +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/adding-patterns.test.js.snap @@ -1,8 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`adding patterns should insert a block pattern 1`] = ` -"<!-- wp:buttons {\\"align\\":\\"center\\"} --> -<div class=\\"wp-block-buttons aligncenter\\"><!-- wp:button {\\"borderRadius\\":2,\\"style\\":{\\"color\\":{\\"background\\":\\"#ba0c49\\",\\"text\\":\\"#fffffa\\"}}} --> +"<!-- wp:buttons {\\"contentJustification\\":\\"center\\"} --> +<div class=\\"wp-block-buttons is-content-justification-center\\"><!-- wp:button {\\"borderRadius\\":2,\\"style\\":{\\"color\\":{\\"background\\":\\"#ba0c49\\",\\"text\\":\\"#fffffa\\"}}} --> <div class=\\"wp-block-button\\"><a class=\\"wp-block-button__link has-text-color has-background\\" style=\\"border-radius:2px;background-color:#ba0c49;color:#fffffa\\">Download now</a></div> <!-- /wp:button --> diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/draggable-block.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/draggable-block.test.js.snap new file mode 100644 index 00000000000000..ae7c7c14b20b04 --- /dev/null +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/draggable-block.test.js.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Draggable block should drag and drop 1`] = ` +"<!-- wp:paragraph --> +<p>1</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>2</p> +<!-- /wp:paragraph -->" +`; + +exports[`Draggable block should drag and drop 2`] = ` +"<!-- wp:paragraph --> +<p>2</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>1</p> +<!-- /wp:paragraph -->" +`; diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/font-size-picker.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/font-size-picker.test.js.snap index bc58980c7ce536..3bf9468e2a0f14 100644 --- a/packages/e2e-tests/specs/editor/various/__snapshots__/font-size-picker.test.js.snap +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/font-size-picker.test.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Font Size Picker should apply a custom font size using the font size input 1`] = ` -"<!-- wp:paragraph {\\"style\\":{\\"typography\\":{\\"fontSize\\":23}}} --> +"<!-- wp:paragraph {\\"style\\":{\\"typography\\":{\\"fontSize\\":\\"23px\\"}}} --> <p style=\\"font-size:23px\\">Paragraph to be made \\"small\\"</p> <!-- /wp:paragraph -->" `; diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap index 02609fe3594712..b390333b640604 100644 --- a/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap @@ -137,5 +137,9 @@ exports[`Multi-block selection should set attributes for multiple paragraphs 1`] exports[`Multi-block selection should use selection direction to determine vertical edge 1`] = ` "<!-- wp:paragraph --> <p>1<br>2.</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>3</p> <!-- /wp:paragraph -->" `; diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/rtl.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/rtl.test.js.snap index a8b94682a45963..19b4e8c305acc2 100644 --- a/packages/e2e-tests/specs/editor/various/__snapshots__/rtl.test.js.snap +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/rtl.test.js.snap @@ -8,11 +8,11 @@ exports[`RTL should arrow navigate 1`] = ` exports[`RTL should arrow navigate between blocks 1`] = ` "<!-- wp:paragraph --> -<p>٠</p> +<p>٠<br>١</p> <!-- /wp:paragraph --> <!-- wp:paragraph --> -<p><br>١١٠<br><br>٢</p> +<p>٠<br>١<br>٢</p> <!-- /wp:paragraph -->" `; @@ -24,11 +24,7 @@ exports[`RTL should merge backward 1`] = ` exports[`RTL should merge forward 1`] = ` "<!-- wp:paragraph --> -<p>٠</p> -<!-- /wp:paragraph --> - -<!-- wp:paragraph --> -<p></p> +<p>٠١</p> <!-- /wp:paragraph -->" `; diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/writing-flow.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/writing-flow.test.js.snap index 3f975a6ee4c0cf..4ab3ea451a77c5 100644 --- a/packages/e2e-tests/specs/editor/various/__snapshots__/writing-flow.test.js.snap +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/writing-flow.test.js.snap @@ -148,6 +148,16 @@ exports[`Writing Flow should navigate around nested inline boundaries 2`] = ` <!-- /wp:paragraph -->" `; +exports[`Writing Flow should navigate contenteditable with normal line height 1`] = ` +"<!-- wp:paragraph --> +<p>1</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p></p> +<!-- /wp:paragraph -->" +`; + exports[`Writing Flow should navigate contenteditable with padding 1`] = ` "<!-- wp:paragraph --> <p>1</p> diff --git a/packages/e2e-tests/specs/editor/various/adding-blocks.test.js b/packages/e2e-tests/specs/editor/various/adding-blocks.test.js index 6c2700e0a28f07..8af2b7cd6b5b5e 100644 --- a/packages/e2e-tests/specs/editor/various/adding-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/adding-blocks.test.js @@ -14,6 +14,29 @@ import { /** @typedef {import('puppeteer').ElementHandle} ElementHandle */ +/** + * Waits for all patterns in the inserter to have a height, which should + * indicate they've been parsed and are visible. + * + * This allows a test to wait for the layout in the inserter menu to stabilize + * before attempting to interact with the menu contents. + */ +async function waitForInserterPatternLoad() { + await page.waitForFunction( () => { + const previewElements = document.querySelectorAll( + '.block-editor-block-preview__container' + ); + + if ( ! previewElements.length ) { + return true; + } + + return Array.from( previewElements ).every( + ( previewElement ) => previewElement.offsetHeight > 0 + ); + } ); +} + describe( 'adding blocks', () => { beforeEach( async () => { await createNewPost(); @@ -214,7 +237,15 @@ describe( 'adding blocks', () => { // Insert a paragraph block. await page.waitForSelector( '.block-editor-inserter__search-input' ); - await page.keyboard.type( 'Paragraph' ); + + // Search for the paragraph block if it's not in the list of blocks shown. + if ( ! page.$( '.editor-block-list-item-paragraph' ) ) { + await page.keyboard.type( 'Paragraph' ); + await page.waitForSelector( '.editor-block-list-item-paragraph' ); + await waitForInserterPatternLoad(); + } + + // Add the block. await page.click( '.editor-block-list-item-paragraph' ); await page.keyboard.type( '2' ); @@ -259,6 +290,10 @@ describe( 'adding blocks', () => { inserterMenuInputSelector ); inserterMenuSearchInput.type( 'cover' ); + // We need to wait a bit after typing otherwise we might an "early" result + // that is going to be "detached" when trying to click on it + // eslint-disable-next-line no-restricted-syntax + await page.waitFor( 100 ); const coverBlock = await page.waitForSelector( '.block-editor-block-types-list .editor-block-list-item-cover' ); @@ -271,6 +306,13 @@ describe( 'adding blocks', () => { // First insert a random Paragraph. await insertBlock( 'Paragraph' ); await page.keyboard.type( 'First paragraph' ); + await insertBlock( 'Image' ); + await showBlockToolbar(); + const paragraphBlock = await page.$( + 'p[aria-label="Paragraph block"]' + ); + paragraphBlock.click(); + await showBlockToolbar(); // Open the global inserter and search for the Heading block. await searchForBlock( 'Heading' ); @@ -287,10 +329,6 @@ describe( 'adding blocks', () => { '.block-editor-block-list__insertion-point-indicator' ); const indicatorRect = await indicator.boundingBox(); - - const paragraphBlock = await page.$( - 'p[aria-label="Paragraph block"]' - ); const paragraphRect = await paragraphBlock.boundingBox(); // The blue line indicator should be below the last block. @@ -312,4 +350,26 @@ describe( 'adding blocks', () => { await page.keyboard.type( 'Paragraph inside group' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + // Check for regression of https://github.com/WordPress/gutenberg/issues/27586 + it( 'closes the main inserter after inserting a single-use block, like the More block', async () => { + await insertBlock( 'More' ); + await page.waitForSelector( + '.edit-post-header-toolbar__inserter-toggle:not(.is-pressed)' + ); + + // The inserter panel should've closed. + const inserterPanels = await page.$$( + '.edit-post-layout__inserter-panel' + ); + expect( inserterPanels.length ).toBe( 0 ); + + // The editable 'Read More' text should be focused. + const isFocusInBlock = await page.evaluate( () => + document + .querySelector( '[data-type="core/more"]' ) + .contains( document.activeElement ) + ); + expect( isFocusInBlock ).toBe( true ); + } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/block-grouping.test.js b/packages/e2e-tests/specs/editor/various/block-grouping.test.js index 110cda048453a8..64292a0500b6cc 100644 --- a/packages/e2e-tests/specs/editor/various/block-grouping.test.js +++ b/packages/e2e-tests/specs/editor/various/block-grouping.test.js @@ -5,6 +5,7 @@ import { insertBlock, createNewPost, clickBlockToolbarButton, + clickMenuItem, pressKeyWithModifier, getEditedPostContent, transformBlockTo, @@ -77,11 +78,7 @@ describe( 'Block Grouping', () => { await pressKeyWithModifier( 'primary', 'a' ); await clickBlockToolbarButton( 'More options' ); - - const groupButton = await page.waitForXPath( - '//button[text()="Group"]' - ); - await groupButton.click(); + await clickMenuItem( 'Group' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -94,19 +91,13 @@ describe( 'Block Grouping', () => { // Group await clickBlockToolbarButton( 'More options' ); - const groupButton = await page.waitForXPath( - '//button[text()="Group"]' - ); - await groupButton.click(); + await clickMenuItem( 'Group' ); expect( await getEditedPostContent() ).toMatchSnapshot(); // UnGroup await clickBlockToolbarButton( 'More options' ); - const unGroupButton = await page.waitForXPath( - '//button[text()="Ungroup"]' - ); - await unGroupButton.click(); + await clickMenuItem( 'Ungroup' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -115,7 +106,7 @@ describe( 'Block Grouping', () => { await insertBlock( 'Group' ); await clickBlockToolbarButton( 'More options' ); const ungroupButtons = await page.$x( - '//button[text()="Ungroup"]' + '//button/span[text()="Ungroup"]' ); expect( ungroupButtons ).toHaveLength( 0 ); } ); @@ -236,10 +227,7 @@ describe( 'Block Grouping', () => { // as opposed to "transformTo()" which uses whatever is passed to it. To // ensure this test is meaningful we must rely on what is registered. await clickBlockToolbarButton( 'More options' ); - const groupButton = await page.waitForXPath( - '//button[text()="Group"]' - ); - await groupButton.click(); + await clickMenuItem( 'Group' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); diff --git a/packages/e2e-tests/specs/editor/various/datepicker.test.js b/packages/e2e-tests/specs/editor/various/datepicker.test.js index 6118f05368ae77..f2203e92e9a5dc 100644 --- a/packages/e2e-tests/specs/editor/various/datepicker.test.js +++ b/packages/e2e-tests/specs/editor/various/datepicker.test.js @@ -1,88 +1,137 @@ /** * WordPress dependencies */ -import { createNewPost } from '@wordpress/e2e-test-utils'; +import { createNewPost, changeSiteTimezone } from '@wordpress/e2e-test-utils'; + +async function getInputValue( selector ) { + return page.$eval( selector, ( element ) => element.value ); +} + +async function getSelectedOptionLabel( selector ) { + return page.$eval( + selector, + ( element ) => element.options[ element.selectedIndex ].text + ); +} + +async function getDatePickerValues() { + const year = await getInputValue( '[aria-label="Year"]' ); + const month = await getInputValue( '[aria-label="Month"]' ); + const monthLabel = await getSelectedOptionLabel( '[aria-label="Month"]' ); + const day = await getInputValue( '[aria-label="Day"]' ); + const hours = await getInputValue( '[aria-label="Hours"]' ); + const minutes = await getInputValue( '[aria-label="Minutes"]' ); + const amOrPm = await page.$eval( + '.components-datetime__time-field-am-pm .is-primary', + ( element ) => element.innerText.toLowerCase() + ); + + return { year, month, monthLabel, day, hours, minutes, amOrPm }; +} + +function trimLeadingZero( str ) { + return str[ 0 ] === '0' ? str.slice( 1 ) : str; +} + +function formatDatePickerValues( { + year, + monthLabel, + day, + hours, + minutes, + amOrPm, +} ) { + const dayTrimmed = trimLeadingZero( day ); + const hoursTrimmed = trimLeadingZero( hours ); + return `${ monthLabel } ${ dayTrimmed }, ${ year } ${ hoursTrimmed }:${ minutes } ${ amOrPm }`; +} + +async function getPublishingDate() { + return page.$eval( + '.edit-post-post-schedule__toggle', + ( dateLabel ) => dateLabel.textContent + ); +} describe( 'Datepicker', () => { - beforeEach( async () => { - await createNewPost(); - } ); + [ 'UTC-10', 'UTC', 'UTC+10' ].forEach( ( timezone ) => { + describe( timezone, () => { + let oldTimezone; + beforeEach( async () => { + oldTimezone = await changeSiteTimezone( timezone ); + await createNewPost(); + } ); + afterEach( async () => { + await changeSiteTimezone( oldTimezone ); + } ); - it( 'should show the publishing date as "Immediately" if the date is not altered', async () => { - const publishingDate = await page.$eval( - '.edit-post-post-schedule__toggle', - ( dateLabel ) => dateLabel.textContent - ); + it( 'should show the publishing date as "Immediately" if the date is not altered', async () => { + const publishingDate = await getPublishingDate(); - expect( publishingDate ).toEqual( 'Immediately' ); - } ); + expect( publishingDate ).toEqual( 'Immediately' ); + } ); - it( 'should show the publishing date if the date is in the past', async () => { - // Open the datepicker. - await page.click( '.edit-post-post-schedule__toggle' ); + it( 'should show the publishing date if the date is in the past', async () => { + // Open the datepicker. + await page.click( '.edit-post-post-schedule__toggle' ); - // Change the publishing date to a year in the past. - await page.click( '.components-datetime__time-field-year' ); - await page.keyboard.press( 'ArrowDown' ); + // Change the publishing date to a year in the past. + await page.click( '.components-datetime__time-field-year' ); + await page.keyboard.press( 'ArrowDown' ); + const datePickerValues = await getDatePickerValues(); - // Close the datepicker. - await page.click( '.edit-post-post-schedule__toggle' ); + // Close the datepicker. + await page.click( '.edit-post-post-schedule__toggle' ); - const publishingDate = await page.$eval( - '.edit-post-post-schedule__toggle', - ( dateLabel ) => dateLabel.textContent - ); + const publishingDate = await getPublishingDate(); - expect( publishingDate ).toMatch( - /[A-Za-z]{3} \d{1,2}, \d{4} \d{1,2}:\d{2} [ap]m/ - ); - } ); + expect( publishingDate ).toBe( + formatDatePickerValues( datePickerValues ) + ); + } ); - it( 'should show the publishing date if the date is in the future', async () => { - // Open the datepicker. - await page.click( '.edit-post-post-schedule__toggle' ); + it( 'should show the publishing date if the date is in the future', async () => { + // Open the datepicker. + await page.click( '.edit-post-post-schedule__toggle' ); - // Change the publishing date to a year in the future. - await page.click( '.components-datetime__time-field-year' ); - await page.keyboard.press( 'ArrowUp' ); + // Change the publishing date to a year in the future. + await page.click( '.components-datetime__time-field-year' ); + await page.keyboard.press( 'ArrowUp' ); + const datePickerValues = await getDatePickerValues(); - // Close the datepicker. - await page.click( '.edit-post-post-schedule__toggle' ); + // Close the datepicker. + await page.click( '.edit-post-post-schedule__toggle' ); - const publishingDate = await page.$eval( - '.edit-post-post-schedule__toggle', - ( dateLabel ) => dateLabel.textContent - ); + const publishingDate = await getPublishingDate(); - expect( publishingDate ).not.toEqual( 'Immediately' ); - // The expected date format will be "Sep 26, 2018 11:52 pm". - expect( publishingDate ).toMatch( - /[A-Za-z]{3} \d{1,2}, \d{4} \d{1,2}:\d{2} [ap]m/ - ); - } ); + expect( publishingDate ).not.toEqual( 'Immediately' ); + // The expected date format will be "Sep 26, 2018 11:52 pm". + expect( publishingDate ).toBe( + formatDatePickerValues( datePickerValues ) + ); + } ); - it( 'should show the publishing date as "Immediately" if the date is cleared', async () => { - // Open the datepicker. - await page.click( '.edit-post-post-schedule__toggle' ); + it( `should show the publishing date as "Immediately" if the date is cleared`, async () => { + // Open the datepicker. + await page.click( '.edit-post-post-schedule__toggle' ); - // Change the publishing date to a year in the future. - await page.click( '.components-datetime__time-field-year' ); - await page.keyboard.press( 'ArrowUp' ); + // Change the publishing date to a year in the future. + await page.click( '.components-datetime__time-field-year' ); + await page.keyboard.press( 'ArrowUp' ); - // Close the datepicker. - await page.click( '.edit-post-post-schedule__toggle' ); + // Close the datepicker. + await page.click( '.edit-post-post-schedule__toggle' ); - // Open the datepicker. - await page.click( '.edit-post-post-schedule__toggle' ); + // Open the datepicker. + await page.click( '.edit-post-post-schedule__toggle' ); - // Clear the date - await page.click( '.components-datetime__date-reset-button' ); + // Clear the date + await page.click( '.components-datetime__date-reset-button' ); - const publishingDate = await page.$eval( - '.edit-post-post-schedule__toggle', - ( dateLabel ) => dateLabel.textContent - ); + const publishingDate = await getPublishingDate(); - expect( publishingDate ).toEqual( 'Immediately' ); + expect( publishingDate ).toEqual( 'Immediately' ); + } ); + } ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/draggable-block.test.js b/packages/e2e-tests/specs/editor/various/draggable-block.test.js new file mode 100644 index 00000000000000..2d0ebc0bb8974f --- /dev/null +++ b/packages/e2e-tests/specs/editor/various/draggable-block.test.js @@ -0,0 +1,98 @@ +/** + * WordPress dependencies + */ +import { + getEditedPostContent, + createNewPost, + deactivatePlugin, + activatePlugin, + showBlockToolbar, +} from '@wordpress/e2e-test-utils'; + +describe( 'Draggable block', () => { + beforeAll( async () => { + await deactivatePlugin( + 'gutenberg-test-plugin-disables-the-css-animations' + ); + } ); + + afterAll( async () => { + await activatePlugin( + 'gutenberg-test-plugin-disables-the-css-animations' + ); + } ); + + beforeEach( async () => { + await createNewPost(); + } ); + + it( 'should drag and drop', async () => { + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + + // Confirm correct setup. + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await showBlockToolbar(); + await page.waitForSelector( '.block-editor-block-mover__drag-handle' ); + + const dragHandle = await page.$( + '.block-editor-block-mover__drag-handle' + ); + const dragHandleRect = await dragHandle.boundingBox(); + const x = dragHandleRect.x + dragHandleRect.width / 2; + const y = dragHandleRect.y + dragHandleRect.height / 2; + + await page.evaluate( () => { + document.addEventListener( 'dragstart', ( event ) => { + window._dataTransfer = JSON.parse( + event.dataTransfer.getData( 'text' ) + ); + } ); + } ); + + await page.mouse.move( x, y ); + await page.mouse.down(); + + await page.mouse.move( x + 10, y + 10, { steps: 10 } ); + + // Confirm dragged state. + await page.waitForSelector( '.block-editor-block-mover__drag-clone' ); + + const paragraph = await page.$( '[data-type="core/paragraph"]' ); + + const paragraphRect = await paragraph.boundingBox(); + const pX = paragraphRect.x + paragraphRect.width / 2; + const pY = paragraphRect.y + paragraphRect.height / 3; + + // Move over upper side of the first paragraph. + await page.mouse.move( pX, pY, { steps: 10 } ); + + // Puppeteer fires the initial `dragstart` event, but no further events. + // Simulating the drop event works. + await paragraph.evaluate( + ( element, clientX, clientY ) => { + const dataTransfer = new DataTransfer(); + dataTransfer.setData( + 'text/plain', + JSON.stringify( window._dataTransfer ) + ); + const event = new DragEvent( 'drop', { + bubbles: true, + clientX, + clientY, + dataTransfer, + } ); + element.dispatchEvent( event ); + }, + pX, + pY + ); + + await page.mouse.up(); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); +} ); diff --git a/packages/e2e-tests/specs/editor/various/duplicating-blocks.test.js b/packages/e2e-tests/specs/editor/various/duplicating-blocks.test.js index a5aadb7766a793..11c95026a88282 100644 --- a/packages/e2e-tests/specs/editor/various/duplicating-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/duplicating-blocks.test.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { + clickMenuItem, createNewPost, insertBlock, getEditedPostContent, @@ -24,10 +25,7 @@ describe( 'Duplicating blocks', () => { await pressKeyWithModifier( 'primary', 'a' ); await clickBlockToolbarButton( 'More options' ); - const duplicateButton = await page.waitForXPath( - '//button[text()="Duplicate"]' - ); - await duplicateButton.click(); + await clickMenuItem( 'Duplicate' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); diff --git a/packages/e2e-tests/specs/editor/various/editor-modes.test.js b/packages/e2e-tests/specs/editor/various/editor-modes.test.js index 53c280301316a7..82e481ead2da25 100644 --- a/packages/e2e-tests/specs/editor/various/editor-modes.test.js +++ b/packages/e2e-tests/specs/editor/various/editor-modes.test.js @@ -4,6 +4,7 @@ import { clickBlockAppender, clickBlockToolbarButton, + clickMenuItem, createNewPost, getCurrentPostContent, switchEditorModeTo, @@ -27,10 +28,7 @@ describe( 'Editing modes (visual/HTML)', () => { // Change editing mode from "Visual" to "HTML". await clickBlockToolbarButton( 'More options' ); - let changeModeButton = await page.waitForXPath( - '//button[text()="Edit as HTML"]' - ); - await changeModeButton.click(); + await clickMenuItem( 'Edit as HTML' ); // Wait for the block to be converted to HTML editing mode. const htmlBlock = await page.$$( @@ -40,10 +38,7 @@ describe( 'Editing modes (visual/HTML)', () => { // Change editing mode from "HTML" back to "Visual". await clickBlockToolbarButton( 'More options' ); - changeModeButton = await page.waitForXPath( - '//button[text()="Edit visually"]' - ); - await changeModeButton.click(); + await clickMenuItem( 'Edit visually' ); // This block should be in "visual" mode by default. visualBlock = await page.$$( @@ -55,10 +50,7 @@ describe( 'Editing modes (visual/HTML)', () => { it( 'should display sidebar in HTML mode', async () => { // Change editing mode from "Visual" to "HTML". await clickBlockToolbarButton( 'More options' ); - const changeModeButton = await page.waitForXPath( - '//button[text()="Edit as HTML"]' - ); - await changeModeButton.click(); + await clickMenuItem( 'Edit as HTML' ); // The font size picker for the paragraph block should appear, even in // HTML editing mode. @@ -71,10 +63,7 @@ describe( 'Editing modes (visual/HTML)', () => { it( 'should update HTML in HTML mode when sidebar is used', async () => { // Change editing mode from "Visual" to "HTML". await clickBlockToolbarButton( 'More options' ); - const changeModeButton = await page.waitForXPath( - '//button[text()="Edit as HTML"]' - ); - await changeModeButton.click(); + await clickMenuItem( 'Edit as HTML' ); // Make sure the paragraph content is rendered as expected. let htmlBlockContent = await page.$eval( diff --git a/packages/e2e-tests/specs/editor/various/invalid-block.test.js b/packages/e2e-tests/specs/editor/various/invalid-block.test.js index 7bb46d364e39ae..3313e819d752c0 100644 --- a/packages/e2e-tests/specs/editor/various/invalid-block.test.js +++ b/packages/e2e-tests/specs/editor/various/invalid-block.test.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { + clickMenuItem, createNewPost, clickBlockAppender, clickBlockToolbarButton, @@ -20,10 +21,7 @@ describe( 'invalid blocks', () => { await clickBlockToolbarButton( 'More options' ); // Change to HTML mode and close the options - const changeModeButton = await page.waitForXPath( - '//button[text()="Edit as HTML"]' - ); - await changeModeButton.click(); + await clickMenuItem( 'Edit as HTML' ); // Focus on the textarea and enter an invalid paragraph await page.click( @@ -41,9 +39,7 @@ describe( 'invalid blocks', () => { '.block-editor-warning__actions button[aria-label="More options"]' ); - // Click on the 'Resolve' button - const [ resolveButton ] = await page.$x( '//button[text()="Resolve"]' ); - await resolveButton.click(); + await clickMenuItem( 'Resolve' ); // Check we get the resolve modal with the appropriate contents const htmlBlockContent = await page.$eval( diff --git a/packages/e2e-tests/specs/editor/various/is-typing.test.js b/packages/e2e-tests/specs/editor/various/is-typing.test.js index 6f6c0f99fa73af..27d253b27128ba 100644 --- a/packages/e2e-tests/specs/editor/various/is-typing.test.js +++ b/packages/e2e-tests/specs/editor/various/is-typing.test.js @@ -25,8 +25,7 @@ describe( 'isTyping', () => { expect( blockToolbar ).toBe( null ); // Moving the mouse shows the toolbar - await page.mouse.move( 0, 0 ); - await page.mouse.move( 10, 10 ); + await showBlockToolbar(); // Toolbar is visible blockToolbar = await page.$( blockToolbarSelector ); diff --git a/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js b/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js index 99a9be8268fe1a..2b092c9b466906 100644 --- a/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js @@ -108,10 +108,10 @@ describe( 'Order of block keyboard navigation', () => { await page.keyboard.type( paragraphBlock ); } - // Clear the selected block and put focus in front of the block list. - await page.evaluate( () => { - document.querySelector( '.edit-post-visual-editor' ).focus(); - } ); + // Clear the selected block. + const paragraph = await page.$( '[data-type="core/paragraph"]' ); + const box = await paragraph.boundingBox(); + await page.mouse.click( box.x - 1, box.y ); await page.keyboard.press( 'Tab' ); await expect( @@ -143,9 +143,13 @@ describe( 'Order of block keyboard navigation', () => { await page.keyboard.type( paragraphBlock ); } - // Clear the selected block and put focus behind the block list. + // Clear the selected block. + const paragraph = await page.$( '[data-type="core/paragraph"]' ); + const box = await paragraph.boundingBox(); + await page.mouse.click( box.x - 1, box.y ); + + // Put focus behind the block list. await page.evaluate( () => { - document.querySelector( '.edit-post-visual-editor' ).focus(); document .querySelector( '.interface-interface-skeleton__sidebar' ) .focus(); diff --git a/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js index 614e4828d5f564..bfa098f7f42406 100644 --- a/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js +++ b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js @@ -148,6 +148,9 @@ describe( 'Multi-block selection', () => { await clickBlockAppender(); await page.keyboard.type( '1' ); await pressKeyWithModifier( 'shift', 'Enter' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '3' ); + await page.keyboard.press( 'ArrowUp' ); await page.keyboard.type( '2' ); await pressKeyWithModifier( 'shift', 'ArrowUp' ); diff --git a/packages/e2e-tests/specs/editor/various/preview.test.js b/packages/e2e-tests/specs/editor/various/preview.test.js index 87c00255c508cf..d0996211a512c9 100644 --- a/packages/e2e-tests/specs/editor/various/preview.test.js +++ b/packages/e2e-tests/specs/editor/various/preview.test.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { last } from 'lodash'; - /** * WordPress dependencies */ @@ -15,31 +10,11 @@ import { deactivatePlugin, publishPost, saveDraft, + openPreviewPage, } from '@wordpress/e2e-test-utils'; /** @typedef {import('puppeteer').Page} Page */ -async function openPreviewPage( editorPage ) { - let openTabs = await browser.pages(); - const expectedTabsCount = openTabs.length + 1; - await editorPage.click( '.block-editor-post-preview__button-toggle' ); - await editorPage.waitFor( '.edit-post-header-preview__button-external' ); - await editorPage.click( '.edit-post-header-preview__button-external' ); - - // Wait for the new tab to open. - while ( openTabs.length < expectedTabsCount ) { - await editorPage.waitFor( 1 ); - openTabs = await browser.pages(); - } - - const previewPage = last( openTabs ); - // Wait for the preview to load. We can't do interstitial detection here, - // because it might load too quickly for us to pick up, so we wait for - // the preview to load by waiting for the title to appear. - await previewPage.waitForSelector( '.entry-title' ); - return previewPage; -} - /** * Given the Page instance for the editor, opens preview drodpdown, and * awaits the presence of the external preview selector. @@ -123,6 +98,7 @@ describe( 'Preview', () => { await editorPage.type( '.editor-post-title__input', 'Hello World' ); const previewPage = await openPreviewPage( editorPage ); + await previewPage.waitForSelector( '.entry-title' ); // When autosave completes for a new post, the URL of the editor should // update to include the ID. Use this to assert on preview URL. @@ -222,6 +198,7 @@ describe( 'Preview', () => { // Open the preview page. const previewPage = await openPreviewPage( editorPage ); + await previewPage.waitForSelector( '.entry-title' ); // Title in preview should match input. let previewTitle = await previewPage.$eval( @@ -282,6 +259,7 @@ describe( 'Preview with Custom Fields enabled', () => { // Open the preview page. const previewPage = await openPreviewPage( editorPage ); + await previewPage.waitForSelector( '.entry-title' ); // Check the title and preview match. let previewTitle = await previewPage.$eval( diff --git a/packages/e2e-tests/specs/editor/various/publish-button.test.js b/packages/e2e-tests/specs/editor/various/publish-button.test.js index 680ca0082c2eda..d9396b7f48c386 100644 --- a/packages/e2e-tests/specs/editor/various/publish-button.test.js +++ b/packages/e2e-tests/specs/editor/various/publish-button.test.js @@ -49,9 +49,10 @@ describe( 'PostPublishButton', () => { await page.$( '.editor-post-publish-button[aria-disabled="true"]' ) ).toBeNull(); - await page.evaluate( () => - window.wp.data.dispatch( 'core/edit-post' ).requestMetaBoxUpdates() - ); + await page.evaluate( () => { + window.wp.data.dispatch( 'core/edit-post' ).requestMetaBoxUpdates(); + return true; + } ); expect( await page.$( '.editor-post-publish-button[aria-disabled="true"]' ) ).not.toBeNull(); diff --git a/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js b/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js index 813ab9686e06d6..0d7c6d8baf236f 100644 --- a/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js @@ -2,22 +2,18 @@ * WordPress dependencies */ import { + clickMenuItem, insertBlock, insertReusableBlock, createNewPost, clickBlockToolbarButton, pressKeyWithModifier, - searchForReusableBlock, getEditedPostContent, trashAllPosts, + visitAdminPage, + toggleGlobalBlockInserter, } from '@wordpress/e2e-test-utils'; -function waitForAndAcceptDialog() { - return new Promise( ( resolve ) => { - page.once( 'dialog', () => resolve() ); - } ); -} - describe( 'Reusable blocks', () => { beforeAll( async () => { await createNewPost(); @@ -42,11 +38,7 @@ describe( 'Reusable blocks', () => { await page.keyboard.type( 'Hello there!' ); await clickBlockToolbarButton( 'More options' ); - - const convertButton = await page.waitForXPath( - '//button[text()="Add to Reusable blocks"]' - ); - await convertButton.click(); + await clickMenuItem( 'Add to Reusable blocks' ); // Wait for creation to finish await page.waitForXPath( @@ -89,11 +81,7 @@ describe( 'Reusable blocks', () => { await page.keyboard.type( 'Hello there!' ); await clickBlockToolbarButton( 'More options' ); - - const convertButton = await page.waitForXPath( - '//button[text()="Add to Reusable blocks"]' - ); - await convertButton.click(); + await clickMenuItem( 'Add to Reusable blocks' ); // Wait for creation to finish await page.waitForXPath( @@ -183,11 +171,7 @@ describe( 'Reusable blocks', () => { await page.keyboard.type( 'Awesome Paragraph' ); await clickBlockToolbarButton( 'More options' ); - - const convertButton = await page.waitForXPath( - '//button[text()="Add to Reusable blocks"]' - ); - await convertButton.click(); + await clickMenuItem( 'Add to Reusable blocks' ); // Wait for creation to finish await page.waitForXPath( @@ -250,35 +234,6 @@ describe( 'Reusable blocks', () => { expect( text ).toMatch( 'Oh! Hello there!' ); } ); - it( 'can be deleted', async () => { - // Insert the reusable block we edited above - await insertReusableBlock( 'Surprised greeting block' ); - - // Delete the block and accept the confirmation dialog - await clickBlockToolbarButton( 'More options' ); - const deleteButton = await page.waitForXPath( - '//button[text()="Remove from Reusable blocks"]' - ); - await Promise.all( [ waitForAndAcceptDialog(), deleteButton.click() ] ); - - // Wait for deletion to finish - await page.waitForXPath( - '//*[contains(@class, "components-snackbar")]/*[text()="Block deleted."]' - ); - - // Check that we have an empty post again - expect( await getEditedPostContent() ).toBe( '' ); - - // Search for the block in the inserter - await searchForReusableBlock( 'Surprised greeting block' ); - - // Check that we couldn't find it - const items = await page.$$( - '.block-editor-block-types-list__item[aria-label="Surprised greeting block"]' - ); - expect( items ).toHaveLength( 0 ); - } ); - it( 'can be created from multiselection', async () => { await createNewPost(); @@ -294,10 +249,7 @@ describe( 'Reusable blocks', () => { // Convert block to a reusable block await clickBlockToolbarButton( 'More options' ); - const convertButton = await page.waitForXPath( - '//button[text()="Add to Reusable blocks"]' - ); - await convertButton.click(); + await clickMenuItem( 'Add to Reusable blocks' ); // Wait for creation to finish await page.waitForXPath( @@ -344,4 +296,47 @@ describe( 'Reusable blocks', () => { // Check that we have two paragraph blocks on the page expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'will not break the editor if empty', async () => { + await insertReusableBlock( 'Awesome block' ); + + await visitAdminPage( 'edit.php', [ 'post_type=wp_block' ] ); + + const [ editButton ] = await page.$x( + `//a[contains(@aria-label, 'Awesome block')]` + ); + await editButton.click(); + + await page.waitForNavigation(); + + // Click the block to give it focus + const blockSelector = 'p[data-title="Paragraph"]'; + await page.waitForSelector( blockSelector ); + await page.click( blockSelector ); + + // Delete the block, leaving the reusable block empty + await clickBlockToolbarButton( 'More options' ); + const deleteButton = await page.waitForXPath( + '//button/span[text()="Remove block"]' + ); + deleteButton.click(); + + // Wait for the Update button to become enabled + const publishButtonSelector = '.editor-post-publish-button__button'; + await page.waitForSelector( + publishButtonSelector + '[aria-disabled="false"]' + ); + + // Save the reusable block + await page.click( publishButtonSelector ); + await page.waitForXPath( + '//*[contains(@class, "components-snackbar")]/*[text()="Reusable Block updated."]' + ); + + await createNewPost(); + + await toggleGlobalBlockInserter(); + + expect( console ).not.toHaveErrored(); + } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/rtl.test.js b/packages/e2e-tests/specs/editor/various/rtl.test.js index 831557b9c52234..67f66ee77cc63a 100644 --- a/packages/e2e-tests/specs/editor/various/rtl.test.js +++ b/packages/e2e-tests/specs/editor/various/rtl.test.js @@ -15,10 +15,12 @@ const ARABIC_TWO = '٢'; describe( 'RTL', () => { beforeEach( async () => { await createNewPost(); + await page.evaluate( () => { + document.querySelector( '.is-root-container' ).dir = 'rtl'; + } ); } ); it( 'should arrow navigate', async () => { - await page.evaluate( () => ( document.dir = 'rtl' ) ); await page.keyboard.press( 'Enter' ); // We need at least three characters as arrow navigation *from* the @@ -36,7 +38,6 @@ describe( 'RTL', () => { } ); it( 'should split', async () => { - await page.evaluate( () => ( document.dir = 'rtl' ) ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( ARABIC_ZERO ); @@ -48,7 +49,6 @@ describe( 'RTL', () => { } ); it( 'should merge backward', async () => { - await page.evaluate( () => ( document.dir = 'rtl' ) ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( ARABIC_ZERO ); @@ -61,7 +61,6 @@ describe( 'RTL', () => { } ); it( 'should merge forward', async () => { - await page.evaluate( () => ( document.dir = 'rtl' ) ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( ARABIC_ZERO ); @@ -75,7 +74,6 @@ describe( 'RTL', () => { } ); it( 'should arrow navigate between blocks', async () => { - await page.evaluate( () => ( document.dir = 'rtl' ) ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( ARABIC_ZERO ); @@ -101,7 +99,6 @@ describe( 'RTL', () => { } ); it( 'should navigate inline boundaries', async () => { - await page.evaluate( () => ( document.dir = 'rtl' ) ); await page.keyboard.press( 'Enter' ); // Wait for rich text editor to load. diff --git a/packages/e2e-tests/specs/editor/various/scheduling.test.js b/packages/e2e-tests/specs/editor/various/scheduling.test.js index 7732f8d8a0e7ce..7cd9bb11140bd4 100644 --- a/packages/e2e-tests/specs/editor/various/scheduling.test.js +++ b/packages/e2e-tests/specs/editor/various/scheduling.test.js @@ -1,11 +1,16 @@ /** * WordPress dependencies */ -import { createNewPost } from '@wordpress/e2e-test-utils'; +import { createNewPost, changeSiteTimezone } from '@wordpress/e2e-test-utils'; -describe( 'Scheduling', () => { - beforeEach( createNewPost ); +async function getPublishButtonText() { + return page.$eval( + '.editor-post-publish-button__button', + ( element ) => element.textContent + ); +} +describe( 'Scheduling', () => { const isDateTimeComponentFocused = () => { return page.evaluate( () => { const dateTimeElement = document.querySelector( @@ -18,7 +23,38 @@ describe( 'Scheduling', () => { } ); }; + [ 'UTC-10', 'UTC', 'UTC+10' ].forEach( ( timezone ) => { + describe( timezone, () => { + let oldTimezone; + beforeEach( async () => { + oldTimezone = await changeSiteTimezone( timezone ); + await createNewPost(); + } ); + afterEach( async () => { + await changeSiteTimezone( oldTimezone ); + } ); + + it( `should change publishing button text from "Publish" to "Schedule"`, async () => { + expect( await getPublishButtonText() ).toBe( 'Publish' ); + + // Open the datepicker. + await page.click( '.edit-post-post-schedule__toggle' ); + + // Change the publishing date to a year in the future. + await page.click( '.components-datetime__time-field-year' ); + await page.keyboard.press( 'ArrowUp' ); + + // Close the datepicker. + await page.click( '.edit-post-post-schedule__toggle' ); + + expect( await getPublishButtonText() ).toBe( 'Schedule…' ); + } ); + } ); + } ); + it( 'Should keep date time UI focused when the previous and next month buttons are clicked', async () => { + await createNewPost(); + await page.click( '.edit-post-post-schedule__toggle' ); await page.click( 'div[aria-label="Move backward to switch to the previous month."]' diff --git a/packages/e2e-tests/specs/editor/various/toolbar-roving-tabindex.test.js b/packages/e2e-tests/specs/editor/various/toolbar-roving-tabindex.test.js index 9e2cc3528d6d4f..b31c088352a63a 100644 --- a/packages/e2e-tests/specs/editor/various/toolbar-roving-tabindex.test.js +++ b/packages/e2e-tests/specs/editor/various/toolbar-roving-tabindex.test.js @@ -4,6 +4,7 @@ import { createNewPost, pressKeyWithModifier, + clickBlockToolbarButton, insertBlock, } from '@wordpress/e2e-test-utils'; @@ -103,4 +104,14 @@ describe( 'Toolbar roving tabindex', () => { await wrapCurrentBlockWithGroup(); await testGroupKeyboardNavigation( 'Block: Custom HTML' ); } ); + + it( 'ensures block toolbar remembers the last focused item', async () => { + await insertBlock( 'Paragraph' ); + await page.keyboard.type( 'Paragraph' ); + await focusBlockToolbar(); + await clickBlockToolbarButton( 'Bold' ); + await page.keyboard.type( 'a' ); + await pressKeyWithModifier( 'shift', 'Tab' ); + await expectLabelToHaveFocus( 'Bold' ); + } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/writing-flow.test.js b/packages/e2e-tests/specs/editor/various/writing-flow.test.js index 42bf2d16909294..f7d8b031f2911c 100644 --- a/packages/e2e-tests/specs/editor/various/writing-flow.test.js +++ b/packages/e2e-tests/specs/editor/various/writing-flow.test.js @@ -397,6 +397,18 @@ describe( 'Writing Flow', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + it( 'should navigate contenteditable with normal line height', async () => { + await clickBlockAppender(); + await page.keyboard.press( 'Enter' ); + await page.evaluate( () => { + document.activeElement.style.lineHeight = 'normal'; + } ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.type( '1' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + it( 'should not prematurely multi-select', async () => { await clickBlockAppender(); await page.keyboard.type( '1' ); diff --git a/packages/e2e-tests/specs/experiments/blocks/post-title.test.js b/packages/e2e-tests/specs/experiments/blocks/post-title.test.js new file mode 100644 index 00000000000000..5e307541ff9cde --- /dev/null +++ b/packages/e2e-tests/specs/experiments/blocks/post-title.test.js @@ -0,0 +1,44 @@ +/** + * WordPress dependencies + */ +import { + activateTheme, + createNewPost, + insertBlock, + pressKeyWithModifier, + saveDraft, +} from '@wordpress/e2e-test-utils'; + +describe( 'Post Title block', () => { + beforeAll( async () => { + await activateTheme( 'twentytwentyone-blocks' ); + } ); + + afterAll( async () => { + await activateTheme( 'twentytwentyone' ); + } ); + + beforeEach( async () => { + await createNewPost(); + } ); + + it( 'Can edit the post title', async () => { + // Create a block with some text that will trigger a list creation. + await insertBlock( 'Post Title' ); + + // Select all of the text in the post title block. + await pressKeyWithModifier( 'primary', 'a' ); + + // Create a second list item. + await page.keyboard.type( 'Just tweaking the post title' ); + + await saveDraft(); + await page.reload(); + await page.waitForSelector( '.edit-post-layout' ); + const title = await page.$eval( + '.editor-post-title__input', + ( element ) => element.value + ); + expect( title ).toEqual( 'Just tweaking the post title' ); + } ); +} ); diff --git a/packages/e2e-tests/specs/experiments/multi-entity-editing.test.js b/packages/e2e-tests/specs/experiments/multi-entity-editing.test.js index ae4322d01475da..3a8e75b31158c6 100644 --- a/packages/e2e-tests/specs/experiments/multi-entity-editing.test.js +++ b/packages/e2e-tests/specs/experiments/multi-entity-editing.test.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { kebabCase } from 'lodash'; - /** * WordPress dependencies */ @@ -12,26 +7,21 @@ import { createNewPost, publishPost, trashAllPosts, + activateTheme, } from '@wordpress/e2e-test-utils'; import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ -import { - useExperimentalFeatures, - navigationPanel, -} from '../../experimental-features'; +import { navigationPanel } from '../../experimental-features'; const visitSiteEditor = async () => { const query = addQueryArgs( '', { page: 'gutenberg-edit-site', } ).slice( 1 ); await visitAdminPage( 'admin.php', query ); - // Waits for the template part to load... - await page.waitForSelector( - '.wp-block[data-type="core/template-part"] .block-editor-block-list__layout' - ); + await page.waitForSelector( '.edit-site-visual-editor' ); }; const clickTemplateItem = async ( menus, itemName ) => { @@ -53,8 +43,8 @@ const createTemplatePart = async ( await createNewButton.click(); await page.waitForSelector( isNested - ? '.wp-block[data-type="core/template-part"] .wp-block[data-type="core/template-part"] .block-editor-block-list__layout' - : '.wp-block[data-type="core/template-part"] .block-editor-block-list__layout' + ? '.wp-block-template-part .wp-block-template-part .block-editor-block-list__layout' + : '.wp-block-template-part .block-editor-block-list__layout' ); await page.focus( '.wp-block-template-part__name-panel input' ); await page.keyboard.type( templatePartName ); @@ -64,8 +54,8 @@ const editTemplatePart = async ( textToAdd, isNested = false ) => { await page.click( `${ isNested - ? '.wp-block[data-type="core/template-part"] .wp-block[data-type="core/template-part"]' - : '.wp-block[data-type="core/template-part"]' + ? '.wp-block-template-part .wp-block-template-part' + : '.wp-block-template-part' } .block-editor-button-block-appender` ); await page.click( '.editor-block-list-item-paragraph' ); @@ -107,17 +97,6 @@ const openEntitySavePanel = async () => { } // If we made it this far, the panel is opened. - // Expand to view savable entities if necessary. - const reviewChangesButton = await page.$( - '.entities-saved-states__review-changes-button' - ); - const [ needsToOpen ] = await reviewChangesButton.$x( - '//*[contains(text(),"Review changes.")]' - ); - if ( needsToOpen ) { - await reviewChangesButton.click(); - } - return true; }; @@ -147,27 +126,23 @@ const removeErrorMocks = () => { }; describe( 'Multi-entity editor states', () => { - // Setup & Teardown. - const templateName = 'Front Page'; - const templatePartName = 'Test Template Part Name Edit'; - const nestedTPName = 'Test Nested Template Part Name Edit'; - - useExperimentalFeatures( [ - '#gutenberg-full-site-editing', - '#gutenberg-full-site-editing-demo', - ] ); - beforeAll( async () => { + await activateTheme( 'twentytwentyone-blocks' ); await trashAllPosts( 'wp_template' ); await trashAllPosts( 'wp_template_part' ); } ); + afterAll( async () => { + await activateTheme( 'twentytwentyone' ); + } ); + it( 'should not display any dirty entities when loading the site editor', async () => { await visitSiteEditor(); expect( await openEntitySavePanel() ).toBe( false ); } ); it( 'should not dirty an entity by switching to it in the template dropdown', async () => { + await visitSiteEditor(); await clickTemplateItem( 'Template Parts', 'header' ); // Wait for blocks to load. @@ -176,7 +151,7 @@ describe( 'Multi-entity editor states', () => { expect( await isEntityDirty( 'front-page' ) ).toBe( false ); // Switch back and make sure it is still clean. - await clickTemplateItem( 'Templates', 'Front page' ); + await clickTemplateItem( 'Templates', 'Front Page' ); await page.waitForSelector( '.wp-block' ); expect( await isEntityDirty( 'header' ) ).toBe( false ); expect( await isEntityDirty( 'front-page' ) ).toBe( false ); @@ -185,12 +160,16 @@ describe( 'Multi-entity editor states', () => { } ); describe( 'Multi-entity edit', () => { + const templatePartName = 'Test Template Part Name Edit'; + const nestedTPName = 'Test Nested Template Part Name Edit'; + const templateName = 'Custom Template'; + beforeAll( async () => { await trashAllPosts( 'wp_template' ); await trashAllPosts( 'wp_template_part' ); await createNewPost( { postType: 'wp_template', - title: kebabCase( templateName ), + title: templateName, } ); await publishPost(); await createTemplatePart( templatePartName ); @@ -205,6 +184,18 @@ describe( 'Multi-entity editor states', () => { ); await saveAllEntities(); await visitSiteEditor(); + + // Wait for site editor to load. + await page.waitForSelector( + '.wp-block-template-part .block-editor-block-list__layout' + ); + + // Our custom template shows up in the " templates > all" menu; let's use it. + clickTemplateItem( [ 'Templates', 'All' ], templateName ); + await page.waitForXPath( + `//p[contains(@class, "edit-site-document-actions__title") and contains(text(), '${ templateName }')]` + ); + removeErrorMocks(); } ); @@ -227,7 +218,7 @@ describe( 'Multi-entity editor states', () => { it( 'should only dirty the child when editing the child', async () => { await page.click( - '.wp-block[data-type="core/template-part"] .wp-block[data-type="core/paragraph"]' + '.wp-block-template-part .wp-block[data-type="core/paragraph"]' ); await page.keyboard.type( 'Some more test words!' ); @@ -238,7 +229,7 @@ describe( 'Multi-entity editor states', () => { it( 'should only dirty the nested entity when editing the nested entity', async () => { await page.click( - '.wp-block[data-type="core/template-part"] .wp-block[data-type="core/template-part"] .wp-block[data-type="core/paragraph"]' + '.wp-block-template-part .wp-block-template-part .wp-block[data-type="core/paragraph"]' ); await page.keyboard.type( 'Nested test words!' ); diff --git a/packages/e2e-tests/specs/experiments/multi-entity-saving.test.js b/packages/e2e-tests/specs/experiments/multi-entity-saving.test.js index cf49b95962b893..1cb3f8aaf773bf 100644 --- a/packages/e2e-tests/specs/experiments/multi-entity-saving.test.js +++ b/packages/e2e-tests/specs/experiments/multi-entity-saving.test.js @@ -7,16 +7,14 @@ import { publishPost, visitAdminPage, trashAllPosts, + activateTheme, } from '@wordpress/e2e-test-utils'; import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ -import { - useExperimentalFeatures, - navigationPanel, -} from '../../experimental-features'; +import { navigationPanel } from '../../experimental-features'; describe( 'Multi-entity save flow', () => { // Selectors - usable between Post/Site editors. @@ -33,17 +31,6 @@ describe( 'Multi-entity save flow', () => { // Reusable assertions across Post/Site editors. const assertAllBoxesChecked = async () => { - // Expand to view savable entities if necessary. - const reviewChangesButton = await page.$( - '.entities-saved-states__review-changes-button' - ); - const [ needsToOpen ] = await reviewChangesButton.$x( - '//*[contains(text(),"Review changes.")]' - ); - if ( needsToOpen ) { - await reviewChangesButton.click(); - } - const checkedBoxes = await page.$$( checkedBoxSelector ); const checkboxInputs = await page.$$( checkboxInputSelector ); expect( checkedBoxes.length - checkboxInputs.length ).toBe( 0 ); @@ -57,23 +44,22 @@ describe( 'Multi-entity save flow', () => { } }; - useExperimentalFeatures( [ - '#gutenberg-full-site-editing', - '#gutenberg-full-site-editing-demo', - ] ); - beforeAll( async () => { + await activateTheme( 'twentytwentyone-blocks' ); await trashAllPosts( 'wp_template' ); await trashAllPosts( 'wp_template_part' ); } ); + afterAll( async () => { + await activateTheme( 'twentytwentyone' ); + } ); + describe( 'Post Editor', () => { // Selectors - Post editor specific. const draftSavedSelector = '.editor-post-saved-state.is-saved'; const multiSaveSelector = '.editor-post-publish-button__button.has-changes-dot'; const savePostSelector = '.editor-post-publish-button__button'; - const disabledSavePostSelector = `${ savePostSelector }[aria-disabled=true]`; const enabledSavePostSelector = `${ savePostSelector }[aria-disabled=false]`; const publishA11ySelector = '.edit-post-layout__toggle-publish-panel-button'; @@ -89,133 +75,105 @@ describe( 'Multi-entity save flow', () => { expect( multiSaveButton ).not.toBeNull(); }; const assertMultiSaveDisabled = async () => { - const multiSaveButton = await page.$( multiSaveSelector ); + const multiSaveButton = await page.waitForSelector( + multiSaveSelector, + { hidden: true } + ); expect( multiSaveButton ).toBeNull(); }; - describe( 'Pre-Publish state', () => { - it( 'Should not trigger multi-entity save button with only post edited', async () => { - await createNewPost(); - // Edit the page some. - await page.click( '.editor-post-title' ); - await page.keyboard.type( 'Test Post...' ); - await page.keyboard.press( 'Enter' ); - - await assertMultiSaveDisabled(); - } ); - - it( 'Should only have publish panel a11y button active with only post edited', async () => { - await assertExistance( publishA11ySelector, true ); - await assertExistance( saveA11ySelector, false ); - await assertExistance( publishPanelSelector, false ); - await assertExistance( savePanelSelector, false ); - } ); - - it( 'Should trigger multi-entity save button once template part edited', async () => { - // Create new template part. - await insertBlock( 'Template Part' ); - const [ createNewButton ] = await page.$x( - createNewButtonSelector - ); - await createNewButton.click(); - await page.waitForSelector( activatedTemplatePartSelector ); - await page.keyboard.press( 'Tab' ); - await page.keyboard.type( 'test-template-part' ); - - // Make some changes in new Template Part. - await page.click( '.block-editor-button-block-appender' ); - await page.click( '.editor-block-list-item-paragraph' ); - await page.keyboard.type( 'some words...' ); - - await assertMultiSaveEnabled(); - - // TODO: Remove when toolbar supports text fields - expect( console ).toHaveWarnedWith( - 'Using custom components as toolbar controls is deprecated. Please use ToolbarItem or ToolbarButton components instead. See: https://developer.wordpress.org/block-editor/components/toolbar-button/#inside-blockcontrols' - ); - } ); - - it( 'Should only have save panel a11y button active after child entities edited', async () => { - await assertExistance( publishA11ySelector, false ); - await assertExistance( saveA11ySelector, true ); - await assertExistance( publishPanelSelector, false ); - await assertExistance( savePanelSelector, false ); - } ); - - it( 'Clicking should open panel with boxes checked by default', async () => { - await page.click( savePostSelector ); - await page.waitForSelector( savePanelSelector ); - await assertAllBoxesChecked(); - } ); - - it( 'Should not show other panels (or their a11y buttons) while save panel opened', async () => { - await assertExistance( publishA11ySelector, false ); - await assertExistance( saveA11ySelector, false ); - await assertExistance( publishPanelSelector, false ); - } ); - - it( 'Publish panel should open after saving, no other panels (or their a11y buttons) should be present', async () => { - // Save entities and wait for publish panel. - await page.click( entitiesSaveSelector ); - await page.waitForSelector( publishPanelSelector ); - - await assertExistance( publishA11ySelector, false ); - await assertExistance( saveA11ySelector, false ); - await assertExistance( savePanelSelector, false ); - - // Close publish panel. - await page.click( closePanelButtonSelector ); - } ); - - it( 'Saving should result in items being saved', async () => { - // Verify post is saved. - const draftSaved = await page.waitForSelector( - draftSavedSelector - ); - expect( draftSaved ).not.toBeNull(); - - // Verify template part is saved. - await assertMultiSaveDisabled(); - } ); - } ); + it( 'Save flow should work as expected.', async () => { + expect.assertions( 27 ); + await createNewPost(); + // Edit the page some. + await page.click( '.editor-post-title' ); + await page.keyboard.type( 'Test Post...' ); + await page.keyboard.press( 'Enter' ); + + // Should not trigger multi-entity save button with only post edited + await assertMultiSaveDisabled(); + + // Should only have publish panel a11y button active with only post edited. + await assertExistance( publishA11ySelector, true ); + await assertExistance( saveA11ySelector, false ); + await assertExistance( publishPanelSelector, false ); + await assertExistance( savePanelSelector, false ); + + // Add a template part and edit it. + await insertBlock( 'Template Part' ); + const [ createNewButton ] = await page.$x( + createNewButtonSelector + ); + await createNewButton.click(); + await page.waitForSelector( activatedTemplatePartSelector ); + await page.keyboard.press( 'Tab' ); + await page.keyboard.type( 'test-template-part' ); + await page.click( '.block-editor-button-block-appender' ); + await page.click( '.editor-block-list-item-paragraph' ); + await page.keyboard.type( 'some words...' ); + + // Should trigger multi-entity save button once template part edited. + await assertMultiSaveEnabled(); + // TODO: Remove when toolbar supports text fields + expect( console ).toHaveWarnedWith( + 'Using custom components as toolbar controls is deprecated. Please use ToolbarItem or ToolbarButton components instead. See: https://developer.wordpress.org/block-editor/components/toolbar-button/#inside-blockcontrols' + ); + + // Should only have save panel a11y button active after child entities edited. + await assertExistance( publishA11ySelector, false ); + await assertExistance( saveA11ySelector, true ); + await assertExistance( publishPanelSelector, false ); + await assertExistance( savePanelSelector, false ); + + // Opening panel has boxes checked by default. + await page.click( savePostSelector ); + await page.waitForSelector( savePanelSelector ); + await assertAllBoxesChecked(); - describe( 'Published state', () => { - it( 'Update button disabled after publish', async () => { - await publishPost(); - const disabledSaveButton = await page.$( - disabledSavePostSelector - ); - expect( disabledSaveButton ).not.toBeNull(); - } ); - - it( 'should not have save a11y button when no changes', async () => { - await assertExistance( saveA11ySelector, false ); - } ); - - it( 'Update button enabled after editing post', async () => { - await page.click( '.editor-post-title' ); - await page.keyboard.type( '...more title!' ); - - // Verify update button is enabled. - const enabledSaveButton = await page.$( - enabledSavePostSelector - ); - expect( enabledSaveButton ).not.toBeNull(); - - // Verify is not for multi-entity saving. - await assertMultiSaveDisabled(); - } ); - - it( 'Multi-save button triggered after editing template part.', async () => { - await page.click( templatePartSelector ); - await page.keyboard.type( '...some more words...' ); - await page.keyboard.press( 'Enter' ); - await assertMultiSaveEnabled(); - } ); - - it( 'save a11y button enables after editing template part', async () => { - await assertExistance( saveA11ySelector, true ); - } ); + // Should not show other panels (or their a11y buttons) while save panel opened. + await assertExistance( publishA11ySelector, false ); + await assertExistance( saveA11ySelector, false ); + await assertExistance( publishPanelSelector, false ); + + // Publish panel should open after saving. + await page.click( entitiesSaveSelector ); + await page.waitForSelector( publishPanelSelector ); + + // No other panels (or their a11y buttons) should be present with publish panel open. + await assertExistance( publishA11ySelector, false ); + await assertExistance( saveA11ySelector, false ); + await assertExistance( savePanelSelector, false ); + + // Close publish panel. + await page.click( closePanelButtonSelector ); + + // Verify saving is disabled. + const draftSaved = await page.waitForSelector( draftSavedSelector ); + expect( draftSaved ).not.toBeNull(); + await assertMultiSaveDisabled(); + await assertExistance( saveA11ySelector, false ); + + await publishPost(); + + // Update the post. + await page.click( '.editor-post-title' ); + await page.keyboard.type( '...more title!' ); + + // Verify update button is enabled. + const enabledSaveButton = await page.$( enabledSavePostSelector ); + expect( enabledSaveButton ).not.toBeNull(); + // Verify multi-entity saving not enabled. + await assertMultiSaveDisabled(); + await assertExistance( saveA11ySelector, false ); + + // Update template part. + await page.click( templatePartSelector ); + await page.keyboard.type( '...some more words...' ); + await page.keyboard.press( 'Enter' ); + + // Multi-entity saving should be enabled. + await assertMultiSaveEnabled(); + await assertExistance( saveA11ySelector, true ); } ); } ); @@ -226,7 +184,8 @@ describe( 'Multi-entity save flow', () => { const disabledSaveSiteSelector = `${ saveSiteSelector }[aria-disabled=true]`; const saveA11ySelector = '.edit-site-editor__toggle-save-panel-button'; - it( 'Should be enabled after edits', async () => { + it( 'Save flow should work as expected', async () => { + expect.assertions( 5 ); // Navigate to site editor. const query = addQueryArgs( '', { page: 'gutenberg-edit-site', @@ -237,7 +196,7 @@ describe( 'Multi-entity save flow', () => { await navigationPanel.open(); await navigationPanel.backToRoot(); await navigationPanel.navigate( 'Templates' ); - await navigationPanel.clickItemByText( 'Front page' ); + await navigationPanel.clickItemByText( 'Front Page' ); await navigationPanel.close(); // Click the first block so that the template part inserts in the right place. @@ -251,24 +210,21 @@ describe( 'Multi-entity save flow', () => { activeSaveSiteSelector ); + // Should be enabled after edits. expect( enabledButton ).not.toBeNull(); - } ); - it( 'save a11y button should be present', async () => { + // Save a11y button should be present. await assertExistance( saveA11ySelector, true ); - } ); - it( 'Clicking button should open panel with boxes checked', async () => { + // Clicking button should open panel with boxes checked. await page.click( activeSaveSiteSelector ); await page.waitForSelector( savePanelSelector ); await assertAllBoxesChecked(); - } ); - it( 'save a11y button should not be present with save panel open', async () => { + // Save a11y button should not be present with save panel open. await assertExistance( saveA11ySelector, false ); - } ); - it( 'Saving should result in items being saved', async () => { + // Saving should result in items being saved. await page.click( entitiesSaveSelector ); const disabledButton = await page.waitForSelector( disabledSaveSiteSelector diff --git a/packages/e2e-tests/specs/experiments/navigation.test.js b/packages/e2e-tests/specs/experiments/navigation.test.js index 7b2a81824de7a6..39eb9d11c3f218 100644 --- a/packages/e2e-tests/specs/experiments/navigation.test.js +++ b/packages/e2e-tests/specs/experiments/navigation.test.js @@ -267,7 +267,7 @@ beforeEach( async () => { afterEach( async () => { await setUpResponseMocking( [] ); } ); -describe( 'Navigation', () => { +describe.skip( 'Navigation', () => { describe( 'Creating from existing Pages', () => { it( 'allows a navigation block to be created using existing pages', async () => { // Mock the response from the Pages endpoint. This is done so that the pages returned are always diff --git a/packages/e2e-tests/specs/experiments/post-editor-template-mode.test.js b/packages/e2e-tests/specs/experiments/post-editor-template-mode.test.js new file mode 100644 index 00000000000000..31a795a1b2d2f4 --- /dev/null +++ b/packages/e2e-tests/specs/experiments/post-editor-template-mode.test.js @@ -0,0 +1,85 @@ +/** + * WordPress dependencies + */ +import { + activateTheme, + createNewPost, + insertBlock, + saveDraft, + trashAllPosts, + openPreviewPage, + openDocumentSettingsSidebar, +} from '@wordpress/e2e-test-utils'; + +describe( 'Post Editor Template mode', () => { + beforeAll( async () => { + await activateTheme( 'twentytwentyone-blocks' ); + await trashAllPosts( 'wp_template' ); + await trashAllPosts( 'wp_template_part' ); + } ); + + afterAll( async () => { + await activateTheme( 'twentytwentyone' ); + } ); + + beforeEach( async () => { + await createNewPost(); + } ); + + it( 'Allow to switch to template mode, edit the template and check the result', async () => { + // Create a random post. + await page.type( '.editor-post-title__input', 'Just an FSE Post' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'Hello World' ); + + // Unselect the blocks. + await page.evaluate( () => { + wp.data.dispatch( 'core/block-editor' ).clearSelectedBlock(); + } ); + + // Save the post + // Saving shouldn't be necessary but unfortunately, + // there's a template resolution bug forcing us to do so. + await saveDraft(); + await page.reload(); + + // Switch to template mode. + await openDocumentSettingsSidebar(); + const switchLink = await page.waitForSelector( + '.edit-post-post-template button' + ); + await switchLink.click(); + + // Check that we switched properly to edit mode. + await page.waitForXPath( + '//*[contains(@class, "components-snackbar")]/*[text()="Editing template. Changes made here affect all posts and pages that use the template."]' + ); + const title = await page.$eval( + '.edit-post-template-title', + ( el ) => el.innerText + ); + expect( title ).toContain( 'Editing template:' ); + + // Edit the template + await insertBlock( 'Paragraph' ); + await page.keyboard.type( + 'Just a random paragraph added to the template' + ); + + // Save changes + const doneButton = await page.waitForXPath( + `//button[contains(text(), 'Apply')]` + ); + await doneButton.click(); + const saveButton = await page.waitForXPath( + `//div[contains(@class, "entities-saved-states__panel-header")]/button[contains(text(), 'Save')]` + ); + await saveButton.click(); + + // Preview changes + const previewPage = await openPreviewPage(); + await previewPage.waitForXPath( + '//p[contains(text(), "Just a random paragraph added to the template")]' + ); + } ); +} ); diff --git a/packages/e2e-tests/specs/experiments/template-part.test.js b/packages/e2e-tests/specs/experiments/template-part.test.js index a0993fce2b119f..e531067f277aaa 100644 --- a/packages/e2e-tests/specs/experiments/template-part.test.js +++ b/packages/e2e-tests/specs/experiments/template-part.test.js @@ -7,30 +7,28 @@ import { disablePrePublishChecks, visitAdminPage, trashAllPosts, + activateTheme, + getAllBlocks, + selectBlockByClientId, + clickBlockToolbarButton, } from '@wordpress/e2e-test-utils'; import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ -import { - useExperimentalFeatures, - navigationPanel, -} from '../../experimental-features'; +import { navigationPanel } from '../../experimental-features'; describe( 'Template Part', () => { - useExperimentalFeatures( [ - '#gutenberg-full-site-editing', - '#gutenberg-full-site-editing-demo', - ] ); - beforeAll( async () => { + await activateTheme( 'twentytwentyone-blocks' ); await trashAllPosts( 'wp_template' ); await trashAllPosts( 'wp_template_part' ); } ); afterAll( async () => { await trashAllPosts( 'wp_template' ); await trashAllPosts( 'wp_template_part' ); + await activateTheme( 'twentytwentyone' ); } ); describe( 'Template part block', () => { @@ -44,7 +42,7 @@ describe( 'Template Part', () => { await page.waitForSelector( '.edit-site-visual-editor' ); } ); - it( 'Should load customizations when in a template even if only the slug and theme attributes are set.', async () => { + async function updateHeader( content ) { // Switch to editing the header template part. await navigationPanel.open(); await navigationPanel.backToRoot(); @@ -53,7 +51,7 @@ describe( 'Template Part', () => { // Edit it. await insertBlock( 'Paragraph' ); - await page.keyboard.type( 'Header Template Part 123' ); + await page.keyboard.type( content ); // Save it. await page.click( '.edit-site-save-button__button' ); @@ -66,13 +64,154 @@ describe( 'Template Part', () => { await navigationPanel.open(); await navigationPanel.backToRoot(); await navigationPanel.navigate( 'Templates' ); - await navigationPanel.clickItemByText( 'Front page' ); + await navigationPanel.clickItemByText( 'Front Page' ); + await navigationPanel.close(); + } + + async function triggerEllipsisMenuItem( textPrompt ) { + await clickBlockToolbarButton( 'More options' ); + const button = await page.waitForXPath( + `//span[contains(text(), "${ textPrompt }")]` + ); + await button.click(); + } + + async function createParagraphAndGetClientId( content ) { + await insertBlock( 'Paragraph' ); + await page.keyboard.type( content ); + const allBlocks = await getAllBlocks(); + const paragraphBlock = allBlocks.find( + ( block ) => + block.name === 'core/paragraph' && + block.attributes.content === content + ); + return paragraphBlock.clientId; + } + + async function assertParagraphInTemplatePart( content ) { + const paragraphInTemplatePart = await page.waitForXPath( + `//*[@data-type="core/template-part"][//p[text()="${ content }"]]` + ); + expect( paragraphInTemplatePart ).not.toBeNull(); + } + + it( 'Should load customizations when in a template even if only the slug and theme attributes are set.', async () => { + await updateHeader( 'Header Template Part 123' ); // Verify that the header template part is updated. - const [ headerTemplatePart ] = await page.$x( - '//*[@data-type="core/template-part"][//p[text()="Header Template Part123"]]' + await assertParagraphInTemplatePart( 'Header Template Part 123' ); + } ); + + it( 'Should detach blocks from template part', async () => { + await updateHeader( 'Header Template Part 456' ); + + const initialTemplateParts = await page.$$( + '.wp-block-template-part' + ); + + // Select the header template part block. + const allBlocks = await getAllBlocks(); + const headerBlock = allBlocks.find( + ( block ) => block.name === 'core/template-part' + ); + await selectBlockByClientId( headerBlock.clientId ); + // TODO: Remove when toolbar supports text fields + expect( console ).toHaveWarnedWith( + 'Using custom components as toolbar controls is deprecated. Please use ToolbarItem or ToolbarButton components instead. See: https://developer.wordpress.org/block-editor/components/toolbar-button/#inside-blockcontrols' + ); + + // Detach blocks from template part using ellipsis menu. + await triggerEllipsisMenuItem( 'Detach blocks from template part' ); + + // Verify there is one less template part on the page. + const finalTemplateParts = await page.$$( + '.wp-block-template-part' + ); + expect( + initialTemplateParts.length - finalTemplateParts.length + ).toBe( 1 ); + + // Verify content of the template part is still present. + const [ expectedContent ] = await page.$x( + '//p[contains(text(), "Header Template Part 456")]' + ); + expect( expectedContent ).not.toBeUndefined(); + } ); + + it( 'Should convert selected block to template part', async () => { + await page.waitForSelector( '.wp-block-template-part' ); + const initialTemplateParts = await page.$$( + '.wp-block-template-part' ); - expect( headerTemplatePart ).not.toBeNull(); + + // Add some block and select it. + const clientId = await createParagraphAndGetClientId( + 'Some block...' + ); + await selectBlockByClientId( clientId ); + + // Convert block to a template part. + await triggerEllipsisMenuItem( 'Make template part' ); + + // Verify new template part is created with expected content. + await assertParagraphInTemplatePart( 'Some block...' ); + + // TODO: Remove when toolbar supports text fields + expect( console ).toHaveWarnedWith( + 'Using custom components as toolbar controls is deprecated. Please use ToolbarItem or ToolbarButton components instead. See: https://developer.wordpress.org/block-editor/components/toolbar-button/#inside-blockcontrols' + ); + + // Verify there is 1 more template part on the page than previously. + const finalTemplateParts = await page.$$( + '.wp-block-template-part' + ); + expect( + finalTemplateParts.length - initialTemplateParts.length + ).toBe( 1 ); + } ); + + it( 'Should convert multiple selected blocks to template part', async () => { + await page.waitForSelector( '.wp-block-template-part' ); + const initialTemplateParts = await page.$$( + '.wp-block-template-part' + ); + + // Add two blocks and select them. + const block1Id = await createParagraphAndGetClientId( + 'Some block #1' + ); + const block2Id = await createParagraphAndGetClientId( + 'Some block #2' + ); + await page.evaluate( + ( id1, id2 ) => { + wp.data + .dispatch( 'core/block-editor' ) + .multiSelect( id1, id2 ); + }, + block1Id, + block2Id + ); + + // Convert block to a template part. + await triggerEllipsisMenuItem( 'Make template part' ); + + // Verify new template part is created with expected content. + await assertParagraphInTemplatePart( 'Some block #1' ); + await assertParagraphInTemplatePart( 'Some block #2' ); + + // TODO: Remove when toolbar supports text fields + expect( console ).toHaveWarnedWith( + 'Using custom components as toolbar controls is deprecated. Please use ToolbarItem or ToolbarButton components instead. See: https://developer.wordpress.org/block-editor/components/toolbar-button/#inside-blockcontrols' + ); + + // Verify there is 1 more template part on the page than previously. + const finalTemplateParts = await page.$$( + '.wp-block-template-part' + ); + expect( + finalTemplateParts.length - initialTemplateParts.length + ).toBe( 1 ); } ); } ); @@ -118,9 +257,7 @@ describe( 'Template Part', () => { expect( console ).toHaveWarnedWith( 'Using custom components as toolbar controls is deprecated. Please use ToolbarItem or ToolbarButton components instead. See: https://developer.wordpress.org/block-editor/components/toolbar-button/#inside-blockcontrols' ); - } ); - it( 'Should preview newly added template part', async () => { await createNewPost(); // Try to insert the template part we created. await insertBlock( 'Template Part' ); @@ -130,10 +267,7 @@ describe( 'Template Part', () => { await chooseExistingButton.click(); const preview = await page.waitForXPath( testContentSelector ); expect( preview ).toBeTruthy(); - } ); - it( 'Should insert template part when preview is selected', async () => { - const [ preview ] = await page.$x( testContentSelector ); await preview.click(); await page.waitForSelector( activatedTemplatePartSelector ); const templatePartContent = await page.waitForXPath( diff --git a/packages/e2e-tests/specs/local/demo.test.js b/packages/e2e-tests/specs/local/demo.test.js index 9cffda7ed37bb1..f3a1433d720888 100644 --- a/packages/e2e-tests/specs/local/demo.test.js +++ b/packages/e2e-tests/specs/local/demo.test.js @@ -4,6 +4,7 @@ import { createEmbeddingMatcher, createJSONResponse, + createNewPost, setUpResponseMocking, visitAdminPage, } from '@wordpress/e2e-test-utils'; @@ -19,13 +20,20 @@ const MOCK_VIMEO_RESPONSE = { describe( 'new editor state', () => { beforeAll( async () => { + // First, make sure that the block editor is properly configured. + await createNewPost(); + await setUpResponseMocking( [ { match: createEmbeddingMatcher( 'https://vimeo.com/22439234' ), onRequestMatch: createJSONResponse( MOCK_VIMEO_RESPONSE ), }, ] ); - await visitAdminPage( 'post-new.php', 'gutenberg-demo' ); + + await Promise.all( [ + visitAdminPage( 'post-new.php', 'gutenberg-demo' ), + page.waitForNavigation( { waitUntil: 'networkidle0' } ), + ] ); } ); it( 'content should load, making the post dirty', async () => { @@ -34,9 +42,9 @@ describe( 'new editor state', () => { return select( 'core/editor' ).isEditedPostDirty(); } ); expect( isDirty ).toBeTruthy(); - } ); - it( 'should be immediately saveable', async () => { + await page.waitForSelector( 'button.editor-post-save-draft' ); + expect( await page.$( 'button.editor-post-save-draft' ) ).toBeTruthy(); } ); } ); diff --git a/packages/e2e-tests/specs/performance/post-editor.test.js b/packages/e2e-tests/specs/performance/post-editor.test.js index b78e654c932980..34436bb5403063 100644 --- a/packages/e2e-tests/specs/performance/post-editor.test.js +++ b/packages/e2e-tests/specs/performance/post-editor.test.js @@ -11,6 +11,8 @@ import { createNewPost, saveDraft, insertBlock, + openGlobalBlockInserter, + closeGlobalBlockInserter, } from '@wordpress/e2e-test-utils'; function readFile( filePath ) { @@ -51,6 +53,18 @@ function isFocusEvent( item ) { return isKeyEvent( item ) && item.args.data.type === 'focus'; } +function isClickEvent( item ) { + return isKeyEvent( item ) && item.args.data.type === 'click'; +} + +function isMouseOverEvent( item ) { + return isKeyEvent( item ) && item.args.data.type === 'mouseover'; +} + +function isMouseOutEvent( item ) { + return isKeyEvent( item ) && item.args.data.type === 'mouseout'; +} + function getEventDurationsForType( trace, filterFunction ) { return trace.traceEvents .filter( filterFunction ) @@ -69,6 +83,17 @@ function getSelectionEventDurations( trace ) { return [ getEventDurationsForType( trace, isFocusEvent ) ]; } +function getClickEventDurations( trace ) { + return [ getEventDurationsForType( trace, isClickEvent ) ]; +} + +function getHoverEventDurations( trace ) { + return [ + getEventDurationsForType( trace, isMouseOverEvent ), + getEventDurationsForType( trace, isMouseOutEvent ), + ]; +} + jest.setTimeout( 1000000 ); describe( 'Post Editor Performance', () => { @@ -77,6 +102,8 @@ describe( 'Post Editor Performance', () => { load: [], type: [], focus: [], + inserterOpen: [], + inserterHover: [], }; const html = readFile( @@ -110,11 +137,61 @@ describe( 'Post Editor Performance', () => { results.load.push( new Date() - startTime ); } - // Measuring typing performance + // Measure time to open inserter await page.waitForSelector( '.edit-post-layout' ); + const traceFile = __dirname + '/trace.json'; + let traceResults; + for ( let j = 0; j < 10; j++ ) { + await page.tracing.start( { + path: traceFile, + screenshots: false, + categories: [ 'devtools.timeline' ], + } ); + await openGlobalBlockInserter(); + await page.tracing.stop(); + + traceResults = JSON.parse( readFile( traceFile ) ); + const [ mouseClickEvents ] = getClickEventDurations( traceResults ); + for ( let k = 0; k < mouseClickEvents.length; k++ ) { + results.inserterOpen.push( mouseClickEvents[ k ] ); + } + await closeGlobalBlockInserter(); + } + + // Measure inserter hover performance + const paragraphBlockItem = + '.block-editor-inserter__menu .editor-block-list-item-paragraph'; + const headingBlockItem = + '.block-editor-inserter__menu .editor-block-list-item-heading'; + await openGlobalBlockInserter(); + await page.waitForSelector( paragraphBlockItem ); + await page.hover( paragraphBlockItem ); + await page.hover( headingBlockItem ); + for ( let j = 0; j < 20; j++ ) { + await page.tracing.start( { + path: traceFile, + screenshots: false, + categories: [ 'devtools.timeline' ], + } ); + await page.hover( paragraphBlockItem ); + await page.hover( headingBlockItem ); + await page.tracing.stop(); + + traceResults = JSON.parse( readFile( traceFile ) ); + const [ mouseOverEvents, mouseOutEvents ] = getHoverEventDurations( + traceResults + ); + for ( let k = 0; k < mouseOverEvents.length; k++ ) { + results.inserterHover.push( + mouseOverEvents[ k ] + mouseOutEvents[ k ] + ); + } + } + await closeGlobalBlockInserter(); + + // Measuring typing performance await insertBlock( 'Paragraph' ); i = 200; - const traceFile = __dirname + '/trace.json'; await page.tracing.start( { path: traceFile, screenshots: false, @@ -125,7 +202,7 @@ describe( 'Post Editor Performance', () => { } await page.tracing.stop(); - let traceResults = JSON.parse( readFile( traceFile ) ); + traceResults = JSON.parse( readFile( traceFile ) ); const [ keyDownEvents, keyPressEvents, diff --git a/packages/e2e-tests/specs/performance/site-editor.test.js b/packages/e2e-tests/specs/performance/site-editor.test.js index 8c199fb50adb44..ef468fe0453e9d 100644 --- a/packages/e2e-tests/specs/performance/site-editor.test.js +++ b/packages/e2e-tests/specs/performance/site-editor.test.js @@ -4,32 +4,29 @@ import { basename, join } from 'path'; import { writeFileSync } from 'fs'; -/** - * Internal dependencies - */ -import { useExperimentalFeatures } from '../../experimental-features'; - /** * WordPress dependencies */ -import { trashAllPosts, visitAdminPage } from '@wordpress/e2e-test-utils'; +import { + trashAllPosts, + visitAdminPage, + activateTheme, +} from '@wordpress/e2e-test-utils'; import { addQueryArgs } from '@wordpress/url'; jest.setTimeout( 1000000 ); describe( 'Site Editor Performance', () => { - useExperimentalFeatures( [ - '#gutenberg-full-site-editing', - '#gutenberg-full-site-editing-demo', - ] ); - beforeAll( async () => { + await activateTheme( 'twentytwentyone-blocks' ); await trashAllPosts( 'wp_template' ); + await trashAllPosts( 'wp_template', 'auto-draft' ); await trashAllPosts( 'wp_template_part' ); } ); afterAll( async () => { await trashAllPosts( 'wp_template' ); await trashAllPosts( 'wp_template_part' ); + await activateTheme( 'twentytwentyone' ); } ); it( 'Loading', async () => { @@ -37,6 +34,8 @@ describe( 'Site Editor Performance', () => { load: [], type: [], focus: [], + inserterOpen: [], + inserterHover: [], }; await visitAdminPage( @@ -52,7 +51,7 @@ describe( 'Site Editor Performance', () => { while ( i-- ) { const startTime = new Date(); await page.reload(); - await page.waitForSelector( '.wp-block' ); + await page.waitForSelector( '.wp-block', { timeout: 120000 } ); results.load.push( new Date() - startTime ); } diff --git a/packages/edit-navigation/package.json b/packages/edit-navigation/package.json index e5c0478a5f400f..d8e6f719225ba2 100644 --- a/packages/edit-navigation/package.json +++ b/packages/edit-navigation/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-navigation", - "version": "1.7.4", + "version": "1.7.5", "private": true, "description": "Module for the Navigation page in WordPress.", "author": "The WordPress Contributors", @@ -23,12 +23,8 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", - "sideEffects": [ - "build-style/**", - "!((src|build|build-module)/components/**)" - ], "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/block-editor": "file:../block-editor", "@wordpress/block-library": "file:../block-library", @@ -51,7 +47,7 @@ "classnames": "^2.2.5", "lodash": "^4.17.19", "rememo": "^3.0.0", - "uuid": "^7.0.2" + "uuid": "^8.3.0" }, "publishConfig": { "access": "public" diff --git a/packages/edit-navigation/src/components/header/add-menu-form.js b/packages/edit-navigation/src/components/header/add-menu-form.js index 3f47f594bd9626..96fa4ce2f4d46d 100644 --- a/packages/edit-navigation/src/components/header/add-menu-form.js +++ b/packages/edit-navigation/src/components/header/add-menu-form.js @@ -10,6 +10,7 @@ import { useState } from '@wordpress/element'; import { useDispatch } from '@wordpress/data'; import { TextControl, Button } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; +import { store as noticesStore } from '@wordpress/notices'; const menuNameMatches = ( menuName ) => ( menu ) => menu.name.toLowerCase() === menuName.toLowerCase(); @@ -17,9 +18,7 @@ const menuNameMatches = ( menuName ) => ( menu ) => export default function AddMenuForm( { menus, onCreate } ) { const [ menuName, setMenuName ] = useState( '' ); - const { createErrorNotice, createInfoNotice } = useDispatch( - 'core/notices' - ); + const { createErrorNotice, createInfoNotice } = useDispatch( noticesStore ); const [ isCreatingMenu, setIsCreatingMenu ] = useState( false ); diff --git a/packages/edit-navigation/src/components/header/index.js b/packages/edit-navigation/src/components/header/index.js index 1713b4b04988f1..4e27a3a24778c3 100644 --- a/packages/edit-navigation/src/components/header/index.js +++ b/packages/edit-navigation/src/components/header/index.js @@ -1,9 +1,19 @@ +/** + * External dependencies + */ +import { find } from 'lodash'; + /** * WordPress dependencies */ -import { useViewportMatch } from '@wordpress/compose'; -import { __ } from '@wordpress/i18n'; -import { Button, SelectControl, Dropdown } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { + Button, + Dropdown, + DropdownMenu, + MenuGroup, + MenuItemsChoice, +} from '@wordpress/components'; /** * Internal dependencies @@ -11,8 +21,28 @@ import { Button, SelectControl, Dropdown } from '@wordpress/components'; import ManageLocations from './manage-locations'; import AddMenuForm from './add-menu-form'; -export default function Header( { menus, selectedMenuId, onSelectMenu } ) { - const isMobileViewport = useViewportMatch( 'small', '<' ); +export default function Header( { + menus, + selectedMenuId, + onSelectMenu, + isPending, +} ) { + const selectedMenu = find( menus, { id: selectedMenuId } ); + const menuName = selectedMenu ? selectedMenu.name : undefined; + let actionHeaderText; + + if ( menuName ) { + actionHeaderText = sprintf( + // translators: Name of the menu being edited, e.g. 'Main Menu'. + __( 'Editing: %s' ), + menuName + ); + } else if ( isPending ) { + // Loading text won't be displayed if menus are preloaded. + actionHeaderText = __( 'Loading …' ); + } else { + actionHeaderText = __( 'No menus available' ); + } return ( <div className="edit-navigation-header"> @@ -21,28 +51,36 @@ export default function Header( { menus, selectedMenuId, onSelectMenu } ) { </h1> <div className="edit-navigation-header__actions"> - <div className="edit-navigation-header__current-menu"> - <SelectControl - label={ __( 'Currently editing' ) } - hideLabelFromVision={ isMobileViewport } - disabled={ ! menus?.length } - value={ selectedMenuId ?? 0 } - options={ - menus?.length - ? menus.map( ( menu ) => ( { - value: menu.id, - label: menu.name, - } ) ) - : [ - { - value: 0, - label: '-', - }, - ] - } - onChange={ onSelectMenu } - /> - </div> + <h2 className="edit-navigation-header__action_header"> + { actionHeaderText } + </h2> + + <DropdownMenu + icon={ null } + toggleProps={ { + showTooltip: false, + children: __( 'Select menu' ), + isTertiary: true, + disabled: ! menus, + __experimentalIsFocusable: true, + } } + popoverProps={ { + position: 'bottom center', + } } + > + { () => ( + <MenuGroup> + <MenuItemsChoice + value={ selectedMenuId } + onSelect={ onSelectMenu } + choices={ menus.map( ( menu ) => ( { + value: menu.id, + label: menu.name, + } ) ) } + /> + </MenuGroup> + ) } + </DropdownMenu> <Dropdown position="bottom center" diff --git a/packages/edit-navigation/src/components/header/style.scss b/packages/edit-navigation/src/components/header/style.scss index 093e9805b6e16b..ae8511d1da01e4 100644 --- a/packages/edit-navigation/src/components/header/style.scss +++ b/packages/edit-navigation/src/components/header/style.scss @@ -12,19 +12,25 @@ // Lining up all the actions in a nice row. .edit-navigation-header__actions { - align-items: center; display: flex; + align-items: baseline; + margin-top: $grid-unit-10; + + .edit-navigation-header__action_header { + font-weight: normal; + font-size: inherit; + margin: 0; + } // Spacing out the actions a little. - .edit-navigation-header__current-menu, + .edit-navigation-header__action_header, .components-dropdown { margin-right: $grid-unit-20; } -} -// The select menu for choosing which menu to edit. -.edit-navigation-header__current-menu .components-base-control__field { - margin: 0; + .components-dropdown:last-child { + margin-right: 0; + } } .edit-navigation-header__create-menu-button { diff --git a/packages/edit-navigation/src/components/layout/index.js b/packages/edit-navigation/src/components/layout/index.js index 8fb1da3aba0071..9eb350b9dade9f 100644 --- a/packages/edit-navigation/src/components/layout/index.js +++ b/packages/edit-navigation/src/components/layout/index.js @@ -3,7 +3,6 @@ */ import { DropZoneProvider, - FocusReturnProvider, Popover, SlotFillProvider, } from '@wordpress/components'; @@ -45,46 +44,45 @@ export default function Layout( { blockEditorSettings } ) { <ErrorBoundary> <SlotFillProvider> <DropZoneProvider> - <FocusReturnProvider> - <BlockEditorKeyboardShortcuts.Register /> - <NavigationEditorShortcuts.Register /> + <BlockEditorKeyboardShortcuts.Register /> + <NavigationEditorShortcuts.Register /> - <Notices /> + <Notices /> - <div className="edit-navigation-layout"> - <Header - menus={ menus } - selectedMenuId={ selectedMenuId } - onSelectMenu={ selectMenu } - /> + <div className="edit-navigation-layout"> + <Header + isPending={ ! navigationPost } + menus={ menus } + selectedMenuId={ selectedMenuId } + onSelectMenu={ selectMenu } + /> - <BlockEditorProvider - value={ blocks } - onInput={ onInput } - onChange={ onChange } - settings={ { - ...blockEditorSettings, - templateLock: 'all', - hasFixedToolbar: true, - } } - > - <Toolbar - isPending={ ! navigationPost } - navigationPost={ navigationPost } - /> - <Editor - isPending={ ! navigationPost } - blocks={ blocks } - /> - <InspectorAdditions - menuId={ selectedMenuId } - onDeleteMenu={ deleteMenu } - /> - </BlockEditorProvider> - </div> + <BlockEditorProvider + value={ blocks } + onInput={ onInput } + onChange={ onChange } + settings={ { + ...blockEditorSettings, + templateLock: 'all', + hasFixedToolbar: true, + } } + > + <Toolbar + isPending={ ! navigationPost } + navigationPost={ navigationPost } + /> + <Editor + isPending={ ! navigationPost } + blocks={ blocks } + /> + <InspectorAdditions + menuId={ selectedMenuId } + onDeleteMenu={ deleteMenu } + /> + </BlockEditorProvider> + </div> - <Popover.Slot /> - </FocusReturnProvider> + <Popover.Slot /> </DropZoneProvider> </SlotFillProvider> </ErrorBoundary> diff --git a/packages/edit-navigation/src/components/layout/shortcuts.js b/packages/edit-navigation/src/components/layout/shortcuts.js index d75d3f176e2e40..9c2e228b4cc0e9 100644 --- a/packages/edit-navigation/src/components/layout/shortcuts.js +++ b/packages/edit-navigation/src/components/layout/shortcuts.js @@ -3,7 +3,10 @@ */ import { useEffect, useCallback } from '@wordpress/element'; import { useDispatch } from '@wordpress/data'; -import { useShortcut } from '@wordpress/keyboard-shortcuts'; +import { + useShortcut, + store as keyboardShortcutsStore, +} from '@wordpress/keyboard-shortcuts'; import { __ } from '@wordpress/i18n'; function NavigationEditorShortcuts( { saveBlocks } ) { @@ -41,7 +44,7 @@ function NavigationEditorShortcuts( { saveBlocks } ) { } function RegisterNavigationEditorShortcuts() { - const { registerShortcut } = useDispatch( 'core/keyboard-shortcuts' ); + const { registerShortcut } = useDispatch( keyboardShortcutsStore ); useEffect( () => { registerShortcut( { name: 'core/edit-navigation/save-menu', diff --git a/packages/edit-navigation/src/components/layout/use-menu-notifications.js b/packages/edit-navigation/src/components/layout/use-menu-notifications.js index 34adaa51e67ecd..16e7076c0011d2 100644 --- a/packages/edit-navigation/src/components/layout/use-menu-notifications.js +++ b/packages/edit-navigation/src/components/layout/use-menu-notifications.js @@ -3,6 +3,7 @@ */ import { useSelect, useDispatch } from '@wordpress/data'; import { useEffect } from '@wordpress/element'; +import { store as noticesStore } from '@wordpress/notices'; export default function useMenuNotifications( menuId ) { const { lastSaveError, lastDeleteError } = useSelect( @@ -20,7 +21,7 @@ export default function useMenuNotifications( menuId ) { [ menuId ] ); - const { createErrorNotice } = useDispatch( 'core/notices' ); + const { createErrorNotice } = useDispatch( noticesStore ); const processError = ( error ) => { const document = new window.DOMParser().parseFromString( diff --git a/packages/edit-navigation/src/components/layout/use-navigation-block-editor.js b/packages/edit-navigation/src/components/layout/use-navigation-block-editor.js index d2d5034878ff61..e9108b50bbefc0 100644 --- a/packages/edit-navigation/src/components/layout/use-navigation-block-editor.js +++ b/packages/edit-navigation/src/components/layout/use-navigation-block-editor.js @@ -9,9 +9,10 @@ import { useEntityBlockEditor } from '@wordpress/core-data'; * Internal dependencies */ import { KIND, POST_TYPE } from '../../store/utils'; +import { store as editNavigationStore } from '../../store'; export default function useNavigationBlockEditor( post ) { - const { createMissingMenuItems } = useDispatch( 'core/edit-navigation' ); + const { createMissingMenuItems } = useDispatch( editNavigationStore ); const [ blocks, onInput, _onChange ] = useEntityBlockEditor( KIND, diff --git a/packages/edit-navigation/src/components/layout/use-navigation-editor.js b/packages/edit-navigation/src/components/layout/use-navigation-editor.js index 590f66ac0df9f7..bdc615828de3bf 100644 --- a/packages/edit-navigation/src/components/layout/use-navigation-editor.js +++ b/packages/edit-navigation/src/components/layout/use-navigation-editor.js @@ -4,6 +4,11 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { useState, useEffect } from '@wordpress/element'; +/** + * Internal dependencies + */ +import { store as editNavigationStore } from '../../store'; + export default function useNavigationEditor() { const menus = useSelect( ( select ) => select( 'core' ).getMenus( { per_page: -1 } ), @@ -20,7 +25,7 @@ export default function useNavigationEditor() { const navigationPost = useSelect( ( select ) => - select( 'core/edit-navigation' ).getNavigationPostForMenu( + select( editNavigationStore ).getNavigationPostForMenu( selectedMenuId ), [ selectedMenuId ] diff --git a/packages/edit-navigation/src/components/notices/index.js b/packages/edit-navigation/src/components/notices/index.js index e45eef3dc70622..21e15fde348d38 100644 --- a/packages/edit-navigation/src/components/notices/index.js +++ b/packages/edit-navigation/src/components/notices/index.js @@ -8,11 +8,12 @@ import { filter } from 'lodash'; */ import { NoticeList, SnackbarList } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; export default function EditNavigationNotices() { - const { removeNotice } = useDispatch( 'core/notices' ); + const { removeNotice } = useDispatch( noticesStore ); const notices = useSelect( - ( select ) => select( 'core/notices' ).getNotices(), + ( select ) => select( noticesStore ).getNotices(), [] ); const dismissibleNotices = filter( notices, { diff --git a/packages/edit-navigation/src/components/toolbar/save-button.js b/packages/edit-navigation/src/components/toolbar/save-button.js index c5adfef8b8ac75..92132f47198d66 100644 --- a/packages/edit-navigation/src/components/toolbar/save-button.js +++ b/packages/edit-navigation/src/components/toolbar/save-button.js @@ -5,8 +5,13 @@ import { useDispatch } from '@wordpress/data'; import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import { store as editNavigationStore } from '../../store'; + export default function SaveButton( { navigationPost } ) { - const { saveNavigationPost } = useDispatch( 'core/edit-navigation' ); + const { saveNavigationPost } = useDispatch( editNavigationStore ); return ( <Button diff --git a/packages/edit-navigation/src/index.js b/packages/edit-navigation/src/index.js index 09516a1c99134a..7b0ad0eb061df4 100644 --- a/packages/edit-navigation/src/index.js +++ b/packages/edit-navigation/src/index.js @@ -6,7 +6,6 @@ import { map, set, flatten, omit, partialRight } from 'lodash'; /** * WordPress dependencies */ -import '@wordpress/notices'; import { registerCoreBlocks, __experimentalRegisterExperimentalCoreBlocks, @@ -168,7 +167,7 @@ export function initialize( id, settings ) { registerCoreBlocks(); if ( process.env.GUTENBERG_PHASE === 2 ) { - __experimentalRegisterExperimentalCoreBlocks( settings ); + __experimentalRegisterExperimentalCoreBlocks(); } settings.__experimentalFetchLinkSuggestions = partialRight( diff --git a/packages/edit-navigation/src/store/actions.js b/packages/edit-navigation/src/store/actions.js index 19e4ba33826340..1b95b2950d37b2 100644 --- a/packages/edit-navigation/src/store/actions.js +++ b/packages/edit-navigation/src/store/actions.js @@ -8,6 +8,7 @@ import { v4 as uuid } from 'uuid'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies @@ -96,7 +97,7 @@ export const saveNavigationPost = serializeProcessing( function* ( post ) { throw new Error(); } yield dispatch( - 'core/notices', + noticesStore, 'createSuccessNotice', __( 'Navigation saved.' ), { @@ -105,7 +106,7 @@ export const saveNavigationPost = serializeProcessing( function* ( post ) { ); } catch ( e ) { yield dispatch( - 'core/notices', + noticesStore, 'createErrorNotice', __( 'There was an error.' ), { diff --git a/packages/edit-navigation/src/store/constants.js b/packages/edit-navigation/src/store/constants.js new file mode 100644 index 00000000000000..8617d69ab054c5 --- /dev/null +++ b/packages/edit-navigation/src/store/constants.js @@ -0,0 +1,4 @@ +/** + * Module Constants + */ +export const STORE_NAME = 'core/edit-navigation'; diff --git a/packages/edit-navigation/src/store/controls.js b/packages/edit-navigation/src/store/controls.js index fb327b8fff8fcc..ef050130aafdec 100644 --- a/packages/edit-navigation/src/store/controls.js +++ b/packages/edit-navigation/src/store/controls.js @@ -8,6 +8,7 @@ import { createRegistryControl } from '@wordpress/data'; * Internal dependencies */ import { menuItemsQuery } from './utils'; +import { STORE_NAME } from './constants'; /** * Trigger an API Fetch request. @@ -72,7 +73,7 @@ export function getMenuItemToClientIdMapping( postId ) { export function getNavigationPostForMenu( menuId ) { return { type: 'SELECT', - registryName: 'core/edit-navigation', + registryName: STORE_NAME, selectorName: 'getNavigationPostForMenu', args: [ menuId ], }; @@ -173,7 +174,6 @@ const controls = { ), }; -const getState = ( registry ) => - registry.stores[ 'core/edit-navigation' ].store.getState(); +const getState = ( registry ) => registry.stores[ STORE_NAME ].store.getState(); export default controls; diff --git a/packages/edit-navigation/src/store/index.js b/packages/edit-navigation/src/store/index.js index 78da40045a09b5..b55979e5d11868 100644 --- a/packages/edit-navigation/src/store/index.js +++ b/packages/edit-navigation/src/store/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { registerStore } from '@wordpress/data'; +import { createReduxStore, register } from '@wordpress/data'; /** * Internal dependencies @@ -11,11 +11,7 @@ import * as resolvers from './resolvers'; import * as selectors from './selectors'; import * as actions from './actions'; import controls from './controls'; - -/** - * Module Constants - */ -const MODULE_KEY = 'core/edit-navigation'; +import { STORE_NAME } from './constants'; /** * Block editor data store configuration. @@ -24,7 +20,7 @@ const MODULE_KEY = 'core/edit-navigation'; * * @type {Object} */ -export const storeConfig = { +const storeConfig = { reducer, controls, selectors, @@ -32,6 +28,13 @@ export const storeConfig = { actions, }; -const store = registerStore( MODULE_KEY, storeConfig ); +/** + * Store definition for the edit navigation namespace. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore + * + * @type {Object} + */ +export const store = createReduxStore( STORE_NAME, storeConfig ); -export default store; +register( store ); diff --git a/packages/edit-navigation/src/store/test/actions.js b/packages/edit-navigation/src/store/test/actions.js index a300950e5e14eb..a01cdbedbc0afd 100644 --- a/packages/edit-navigation/src/store/test/actions.js +++ b/packages/edit-navigation/src/store/test/actions.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies @@ -346,7 +347,7 @@ describe( 'saveNavigationPost', () => { expect( action.next( { success: true } ).value ).toEqual( dispatch( - 'core/notices', + noticesStore, 'createSuccessNotice', __( 'Navigation saved.' ), { @@ -476,7 +477,7 @@ describe( 'saveNavigationPost', () => { expect( action.next( { success: false } ).value ).toEqual( dispatch( - 'core/notices', + noticesStore, 'createErrorNotice', __( 'There was an error.' ), { diff --git a/packages/edit-navigation/src/store/test/controls.js b/packages/edit-navigation/src/store/test/controls.js index 5e10991c294555..2b784d1e0322ce 100644 --- a/packages/edit-navigation/src/store/test/controls.js +++ b/packages/edit-navigation/src/store/test/controls.js @@ -17,6 +17,7 @@ import controls, { dispatch, } from '../controls'; import { menuItemsQuery } from '../utils'; +import { STORE_NAME } from '../constants'; // Mock it to prevent calling window.fetch in test environment jest.mock( '@wordpress/api-fetch', () => jest.fn( ( request ) => request ) ); @@ -63,7 +64,7 @@ describe( 'getNavigationPostForMenu', () => { it( 'has the correct type and payload', () => { expect( getNavigationPostForMenu( 123 ) ).toEqual( { type: 'SELECT', - registryName: 'core/edit-navigation', + registryName: STORE_NAME, selectorName: 'getNavigationPostForMenu', args: [ 123 ], } ); @@ -144,7 +145,7 @@ describe( 'controls', () => { }; const registry = { stores: { - 'core/edit-navigation': { + [ STORE_NAME ]: { store: { getState: jest.fn( () => state ), }, @@ -157,7 +158,7 @@ describe( 'controls', () => { ).toEqual( [ 'action1', 'action2' ] ); expect( - registry.stores[ 'core/edit-navigation' ].store.getState + registry.stores[ STORE_NAME ].store.getState ).toHaveBeenCalledTimes( 1 ); expect( @@ -167,7 +168,7 @@ describe( 'controls', () => { ).toEqual( [] ); expect( - registry.stores[ 'core/edit-navigation' ].store.getState + registry.stores[ STORE_NAME ].store.getState ).toHaveBeenCalledTimes( 2 ); } ); @@ -181,7 +182,7 @@ describe( 'controls', () => { }; const registry = { stores: { - 'core/edit-navigation': { + [ STORE_NAME ]: { store: { getState: jest.fn( () => state ), }, @@ -194,7 +195,7 @@ describe( 'controls', () => { ).toBe( true ); expect( - registry.stores[ 'core/edit-navigation' ].store.getState + registry.stores[ STORE_NAME ].store.getState ).toHaveBeenCalledTimes( 1 ); expect( @@ -204,7 +205,7 @@ describe( 'controls', () => { ).toBe( false ); expect( - registry.stores[ 'core/edit-navigation' ].store.getState + registry.stores[ STORE_NAME ].store.getState ).toHaveBeenCalledTimes( 2 ); } ); @@ -218,7 +219,7 @@ describe( 'controls', () => { }; const registry = { stores: { - 'core/edit-navigation': { + [ STORE_NAME ]: { store: { getState: jest.fn( () => state ), }, @@ -235,7 +236,7 @@ describe( 'controls', () => { } ); expect( - registry.stores[ 'core/edit-navigation' ].store.getState + registry.stores[ STORE_NAME ].store.getState ).toHaveBeenCalledTimes( 1 ); expect( @@ -245,7 +246,7 @@ describe( 'controls', () => { ).toEqual( {} ); expect( - registry.stores[ 'core/edit-navigation' ].store.getState + registry.stores[ STORE_NAME ].store.getState ).toHaveBeenCalledTimes( 2 ); } ); diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 84135c3fcf0d12..7480f16262b4b7 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "3.25.4", + "version": "3.25.5", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -22,12 +22,8 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", - "sideEffects": [ - "build-style/**", - "!((src|build|build-module)/(components|utils)/**)" - ], "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/a11y": "file:../a11y", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/block-editor": "file:../block-editor", @@ -50,14 +46,12 @@ "@wordpress/notices": "file:../notices", "@wordpress/plugins": "file:../plugins", "@wordpress/primitives": "file:../primitives", - "@wordpress/reusable-blocks": "file:../reusable-blocks", "@wordpress/url": "file:../url", "@wordpress/viewport": "file:../viewport", "@wordpress/warning": "file:../warning", "classnames": "^2.2.5", "lodash": "^4.17.19", "memize": "^1.1.0", - "refx": "^3.0.0", "rememo": "^3.0.0" }, "publishConfig": { diff --git a/packages/edit-post/src/components/admin-notices/index.js b/packages/edit-post/src/components/admin-notices/index.js index 05cfa0676a5559..1906f65824f2c0 100644 --- a/packages/edit-post/src/components/admin-notices/index.js +++ b/packages/edit-post/src/components/admin-notices/index.js @@ -3,6 +3,7 @@ */ import { Component } from '@wordpress/element'; import { withDispatch } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; /** * Mapping of server-supported notice class names to an equivalent notices @@ -103,7 +104,7 @@ export class AdminNotices extends Component { } export default withDispatch( ( dispatch ) => { - const { createNotice } = dispatch( 'core/notices' ); + const { createNotice } = dispatch( noticesStore ); return { createNotice }; } )( AdminNotices ); diff --git a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js index 98eed14996ae60..5f8712b3611f47 100644 --- a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js +++ b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js @@ -9,7 +9,6 @@ import { difference } from 'lodash'; import { BlockSettingsMenuControls } from '@wordpress/block-editor'; import { MenuItem } from '@wordpress/components'; import { compose } from '@wordpress/compose'; -import { plugins } from '@wordpress/icons'; const isEverySelectedBlockAllowed = ( selected, allowed ) => difference( selected, allowed ).length === 0; @@ -102,7 +101,7 @@ const PluginBlockSettingsMenuItem = ( { return ( <MenuItem onClick={ compose( onClick, onClose ) } - icon={ icon || plugins } + icon={ icon } label={ small ? label : undefined } role={ role } > diff --git a/packages/edit-post/src/components/editor-initialization/listener-hooks.js b/packages/edit-post/src/components/editor-initialization/listener-hooks.js index 91af99219220d0..6b5b75b1155672 100644 --- a/packages/edit-post/src/components/editor-initialization/listener-hooks.js +++ b/packages/edit-post/src/components/editor-initialization/listener-hooks.js @@ -8,7 +8,7 @@ import { useEffect, useRef } from '@wordpress/element'; * Internal dependencies */ import { - STORE_KEY, + STORE_NAME, VIEW_AS_LINK_SELECTOR, VIEW_AS_PREVIEW_LINK_SELECTOR, } from '../../store/constants'; @@ -25,12 +25,12 @@ export const useBlockSelectionListener = ( postId ) => { hasBlockSelection: !! select( 'core/block-editor' ).getBlockSelectionStart(), - isEditorSidebarOpened: select( STORE_KEY ).isEditorSidebarOpened(), + isEditorSidebarOpened: select( STORE_NAME ).isEditorSidebarOpened(), } ), [ postId ] ); - const { openGeneralSidebar } = useDispatch( STORE_KEY ); + const { openGeneralSidebar } = useDispatch( STORE_NAME ); useEffect( () => { if ( ! isEditorSidebarOpened ) { diff --git a/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js b/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js index df53ba815dd575..df18799c1cfa86 100644 --- a/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js +++ b/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js @@ -6,7 +6,7 @@ import TestRenderer, { act } from 'react-test-renderer'; /** * WordPress dependencies */ -import { RegistryProvider } from '@wordpress/data'; +import { RegistryProvider, createRegistry } from '@wordpress/data'; /** * Internal dependencies @@ -15,45 +15,65 @@ import { useBlockSelectionListener, useUpdatePostLinkListener, } from '../listener-hooks'; -import { STORE_KEY } from '../../../store/constants'; +import { STORE_NAME } from '../../../store/constants'; describe( 'listener hook tests', () => { + const storeConfig = { + actions: { + forceUpdate: jest.fn( () => ( { type: 'FORCE_UPDATE' } ) ), + }, + reducer: ( state = {}, action ) => + action.type === 'FORCE_UPDATE' ? { ...state } : state, + }; const mockStores = { 'core/block-editor': { - getBlockSelectionStart: jest.fn(), + ...storeConfig, + selectors: { + getBlockSelectionStart: jest.fn(), + }, }, 'core/editor': { - getCurrentPost: jest.fn(), + ...storeConfig, + selectors: { + getCurrentPost: jest.fn(), + }, }, 'core/viewport': { - isViewportMatch: jest.fn(), + ...storeConfig, + selectors: { + isViewportMatch: jest.fn(), + }, }, - [ STORE_KEY ]: { - isEditorSidebarOpened: jest.fn(), - openGeneralSidebar: jest.fn(), - closeGeneralSidebar: jest.fn(), - getActiveGeneralSidebarName: jest.fn(), - }, - }; - let subscribeTrigger; - const registry = { - select: jest - .fn() - .mockImplementation( ( storeName ) => mockStores[ storeName ] ), - dispatch: jest - .fn() - .mockImplementation( ( storeName ) => mockStores[ storeName ] ), - subscribe: ( subscription ) => { - subscribeTrigger = subscription; + [ STORE_NAME ]: { + ...storeConfig, + actions: { + ...storeConfig.actions, + openGeneralSidebar: jest.fn( () => ( { + type: 'OPEN_GENERAL_SIDEBAR', + } ) ), + closeGeneralSidebar: jest.fn( () => ( { + type: 'CLOSE_GENERAL_SIDEBAR', + } ) ), + }, + selectors: { + isEditorSidebarOpened: jest.fn(), + getActiveGeneralSidebarName: jest.fn(), + }, }, }; + + let registry; + beforeEach( () => { + registry = createRegistry( mockStores ); + } ); + const setMockReturnValue = ( store, functionName, value ) => { - mockStores[ store ][ functionName ] = jest - .fn() - .mockReturnValue( value ); + mockStores[ store ].selectors[ functionName ].mockReturnValue( value ); }; const getSpyedFunction = ( store, functionName ) => - mockStores[ store ][ functionName ]; + mockStores[ store ].selectors[ functionName ]; + const getSpyedAction = ( store, actionName ) => + mockStores[ store ].actions[ actionName ]; const renderComponent = ( testedHook, id, renderer = null ) => { const TestComponent = ( { postId } ) => { testedHook( postId ); @@ -70,27 +90,29 @@ describe( 'listener hook tests', () => { }; afterEach( () => { Object.values( mockStores ).forEach( ( storeMocks ) => { - Object.values( storeMocks ).forEach( ( mock ) => { + Object.values( storeMocks.selectors ).forEach( ( mock ) => { + mock.mockClear(); + } ); + Object.values( storeMocks.actions || {} ).forEach( ( mock ) => { mock.mockClear(); } ); } ); - subscribeTrigger = undefined; } ); describe( 'useBlockSelectionListener', () => { it( 'does nothing when editor sidebar is not open', () => { - setMockReturnValue( STORE_KEY, 'isEditorSidebarOpened', false ); + setMockReturnValue( STORE_NAME, 'isEditorSidebarOpened', false ); act( () => { renderComponent( useBlockSelectionListener, 10 ); } ); expect( - getSpyedFunction( STORE_KEY, 'isEditorSidebarOpened' ) + getSpyedFunction( STORE_NAME, 'isEditorSidebarOpened' ) ).toHaveBeenCalled(); expect( - getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + getSpyedAction( STORE_NAME, 'openGeneralSidebar' ) ).toHaveBeenCalledTimes( 0 ); } ); it( 'opens block sidebar if block is selected', () => { - setMockReturnValue( STORE_KEY, 'isEditorSidebarOpened', true ); + setMockReturnValue( STORE_NAME, 'isEditorSidebarOpened', true ); setMockReturnValue( 'core/block-editor', 'getBlockSelectionStart', @@ -100,11 +122,11 @@ describe( 'listener hook tests', () => { renderComponent( useBlockSelectionListener, 10 ); } ); expect( - getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + getSpyedAction( STORE_NAME, 'openGeneralSidebar' ) ).toHaveBeenCalledWith( 'edit-post/block' ); } ); it( 'opens document sidebar if block is not selected', () => { - setMockReturnValue( STORE_KEY, 'isEditorSidebarOpened', true ); + setMockReturnValue( STORE_NAME, 'isEditorSidebarOpened', true ); setMockReturnValue( 'core/block-editor', 'getBlockSelectionStart', @@ -114,7 +136,7 @@ describe( 'listener hook tests', () => { renderComponent( useBlockSelectionListener, 10 ); } ); expect( - getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + getSpyedAction( STORE_NAME, 'openGeneralSidebar' ) ).toHaveBeenCalledWith( 'edit-post/document' ); } ); } ); @@ -149,6 +171,9 @@ describe( 'listener hook tests', () => { expect( setAttribute ).not.toHaveBeenCalled(); } ); it( 'only calls document query selector once across renders', () => { + setMockReturnValue( 'core/editor', 'getCurrentPost', { + link: 'foo', + } ); act( () => { const renderer = renderComponent( useUpdatePostLinkListener, @@ -158,7 +183,7 @@ describe( 'listener hook tests', () => { } ); expect( mockSelector ).toHaveBeenCalledTimes( 1 ); act( () => { - subscribeTrigger(); + registry.dispatch( 'core/editor' ).forceUpdate(); } ); expect( mockSelector ).toHaveBeenCalledTimes( 1 ); } ); @@ -169,8 +194,9 @@ describe( 'listener hook tests', () => { act( () => { renderComponent( useUpdatePostLinkListener, 10 ); } ); + expect( setAttribute ).toHaveBeenCalledTimes( 1 ); act( () => { - subscribeTrigger(); + registry.dispatch( 'core/editor' ).forceUpdate(); } ); expect( setAttribute ).toHaveBeenCalledTimes( 1 ); } ); @@ -181,11 +207,14 @@ describe( 'listener hook tests', () => { act( () => { renderComponent( useUpdatePostLinkListener, 10 ); } ); + expect( setAttribute ).toHaveBeenCalledTimes( 1 ); + expect( setAttribute ).toHaveBeenCalledWith( 'href', 'foo' ); + setMockReturnValue( 'core/editor', 'getCurrentPost', { link: 'bar', } ); act( () => { - subscribeTrigger(); + registry.dispatch( 'core/editor' ).forceUpdate(); } ); expect( setAttribute ).toHaveBeenCalledTimes( 2 ); expect( setAttribute ).toHaveBeenCalledWith( 'href', 'bar' ); diff --git a/packages/edit-post/src/components/header/fullscreen-mode-close/index.js b/packages/edit-post/src/components/header/fullscreen-mode-close/index.js index d7da08c758470f..93d7f680487930 100644 --- a/packages/edit-post/src/components/header/fullscreen-mode-close/index.js +++ b/packages/edit-post/src/components/header/fullscreen-mode-close/index.js @@ -12,7 +12,7 @@ import { __ } from '@wordpress/i18n'; import { addQueryArgs } from '@wordpress/url'; import { wordpress } from '@wordpress/icons'; -function FullscreenModeClose( { showTooltip } ) { +function FullscreenModeClose( { showTooltip, icon, href } ) { const { isActive, isRequestingSiteIcon, postType, siteIconUrl } = useSelect( ( select ) => { const { getCurrentPostType } = select( 'core/editor' ); @@ -50,16 +50,26 @@ function FullscreenModeClose( { showTooltip } ) { src={ siteIconUrl } /> ); - } else if ( isRequestingSiteIcon ) { + } + + if ( isRequestingSiteIcon ) { buttonIcon = null; } + // Override default icon if custom icon is provided via props. + if ( icon ) { + buttonIcon = <Icon size="36px" icon={ icon } />; + } + return ( <Button className="edit-post-fullscreen-mode-close has-icon" - href={ addQueryArgs( 'edit.php', { - post_type: postType.slug, - } ) } + href={ + href ?? + addQueryArgs( 'edit.php', { + post_type: postType.slug, + } ) + } label={ get( postType, [ 'labels', 'view_items' ], __( 'Back' ) ) } showTooltip={ showTooltip } > diff --git a/packages/edit-post/src/components/header/fullscreen-mode-close/style.scss b/packages/edit-post/src/components/header/fullscreen-mode-close/style.scss index 825e2811d5c979..d24dd609328526 100644 --- a/packages/edit-post/src/components/header/fullscreen-mode-close/style.scss +++ b/packages/edit-post/src/components/header/fullscreen-mode-close/style.scss @@ -11,7 +11,7 @@ background: #23282e; // WP-admin gray. color: $white; border-radius: 0; - height: auto; + height: $header-height; width: $header-height; &:hover { @@ -31,4 +31,3 @@ .edit-post-fullscreen-mode-close_site-icon { width: 36px; } - diff --git a/packages/edit-post/src/components/header/header-toolbar/index.js b/packages/edit-post/src/components/header/header-toolbar/index.js index ff8226f101a08b..1349d67ffbc612 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -25,11 +30,15 @@ import { import { plus } from '@wordpress/icons'; import { useRef } from '@wordpress/element'; +/** + * Internal dependencies + */ +import TemplateTitle from '../template-title'; + function HeaderToolbar() { const inserterButton = useRef(); const { setIsInserterOpened } = useDispatch( 'core/edit-post' ); const { - hasReducedUI, hasFixedToolbar, isInserterEnabled, isInserterOpened, @@ -37,6 +46,7 @@ function HeaderToolbar() { previewDeviceType, showIconLabels, isNavigationTool, + isTemplateMode, } = useSelect( ( select ) => { const { hasInserterItems, @@ -65,9 +75,7 @@ function HeaderToolbar() { 'showIconLabels' ), isNavigationTool: select( 'core/block-editor' ).isNavigationMode(), - hasReducedUI: select( 'core/edit-post' ).isFeatureActive( - 'reducedUI' - ), + isTemplateMode: select( 'core/edit-post' ).isEditingTemplate(), }; }, [] ); const isLargeViewport = useViewportMatch( 'medium' ); @@ -141,7 +149,7 @@ function HeaderToolbar() { > { showIconLabels && __( 'Add' ) } </ToolbarItem> - { ! hasReducedUI && ( isWideViewport || ! showIconLabels ) && ( + { ( isWideViewport || ! showIconLabels ) && ( <> { isLargeViewport && ( <ToolbarItem @@ -164,59 +172,63 @@ function HeaderToolbar() { { overflowItems } </> ) } - { ! hasReducedUI && - ! isWideViewport && - ! isSmallViewport && - showIconLabels && ( - <DropdownMenu - position="bottom right" - label={ - /* translators: button label text should, if possible, be under 16 - characters. */ - __( 'Tools' ) - } - > - { () => ( - <div className="edit-post-header__dropdown"> - <MenuGroup label={ __( 'Modes' ) }> - <MenuItemsChoice - value={ - isNavigationTool - ? 'select' - : 'edit' - } - onSelect={ onSwitchMode } - choices={ [ - { - value: 'edit', - label: __( 'Edit' ), - }, - { - value: 'select', - label: __( 'Select' ), - }, - ] } - /> - </MenuGroup> - <MenuGroup label={ __( 'Edit' ) }> - <EditorHistoryUndo - showTooltip={ ! showIconLabels } - isTertiary={ showIconLabels } - /> - <EditorHistoryRedo - showTooltip={ ! showIconLabels } - isTertiary={ showIconLabels } - /> - </MenuGroup> - <MenuGroup>{ overflowItems }</MenuGroup> - </div> - ) } - </DropdownMenu> - ) } + { ! isWideViewport && ! isSmallViewport && showIconLabels && ( + <DropdownMenu + position="bottom right" + label={ + /* translators: button label text should, if possible, be under 16 +characters. */ + __( 'Tools' ) + } + > + { () => ( + <div className="edit-post-header__dropdown"> + <MenuGroup label={ __( 'Modes' ) }> + <MenuItemsChoice + value={ + isNavigationTool ? 'select' : 'edit' + } + onSelect={ onSwitchMode } + choices={ [ + { + value: 'edit', + label: __( 'Edit' ), + }, + { + value: 'select', + label: __( 'Select' ), + }, + ] } + /> + </MenuGroup> + <MenuGroup label={ __( 'Edit' ) }> + <EditorHistoryUndo + showTooltip={ ! showIconLabels } + isTertiary={ showIconLabels } + /> + <EditorHistoryRedo + showTooltip={ ! showIconLabels } + isTertiary={ showIconLabels } + /> + </MenuGroup> + <MenuGroup>{ overflowItems }</MenuGroup> + </div> + ) } + </DropdownMenu> + ) } </div> + <TemplateTitle /> + { displayBlockToolbar && ( - <div className="edit-post-header-toolbar__block-toolbar"> + <div + className={ classnames( + 'edit-post-header-toolbar__block-toolbar', + { + 'is-pushed-down': isTemplateMode, + } + ) } + > <BlockToolbar hideDragHandle /> </div> ) } diff --git a/packages/edit-post/src/components/header/header-toolbar/style.scss b/packages/edit-post/src/components/header/header-toolbar/style.scss index 213f27cfd31b7d..fc3bd59f4232fe 100644 --- a/packages/edit-post/src/components/header/header-toolbar/style.scss +++ b/packages/edit-post/src/components/header/header-toolbar/style.scss @@ -1,5 +1,6 @@ .edit-post-header-toolbar { display: inline-flex; + flex-grow: 1; align-items: center; border: none; @@ -48,6 +49,24 @@ } } +// Reduced UI. +.edit-post-header.has-reduced-ui { + @include break-small () { + // Apply transition to every button but the first one. + .edit-post-header-toolbar__left > * + .components-button, + .edit-post-header-toolbar__left > * + .components-dropdown > [aria-expanded="false"] { + transition: opacity 0.1s linear; + @include reduce-motion("transition"); + } + + // Zero out opacity unless hovered. + &:not(:hover) .edit-post-header-toolbar__left > * + .components-button, + &:not(:hover) .edit-post-header-toolbar__left > * + .components-dropdown > [aria-expanded="false"] { + opacity: 0; + } + } +} + .edit-post-header-toolbar__left { display: inline-flex; align-items: center; @@ -99,25 +118,27 @@ // Move toolbar into top Editor Bar. @include break-wide { - position: static; - left: auto; - right: auto; - background: none; - border-bottom: none; - - .is-sidebar-opened & { + &:not(.is-pushed-down) { + position: static; + left: auto; right: auto; - } + background: none; + border-bottom: none; - .block-editor-block-toolbar { - border-left: $border-width solid $gray-300; - } + .is-sidebar-opened & { + right: auto; + } + + .block-editor-block-toolbar { + border-left: $border-width solid $gray-300; + } - .block-editor-block-toolbar .components-toolbar-group, - .block-editor-block-toolbar .components-toolbar { - $top-toolbar-padding: ($header-height - $grid-unit-60) / 2; - height: $header-height; - padding: $top-toolbar-padding 0; + .block-editor-block-toolbar .components-toolbar-group, + .block-editor-block-toolbar .components-toolbar { + $top-toolbar-padding: ($header-height - $grid-unit-60) / 2; + height: $header-height; + padding: $top-toolbar-padding 0; + } } } } diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js index a23b7f10c86fca..639ba8424aeab8 100644 --- a/packages/edit-post/src/components/header/index.js +++ b/packages/edit-post/src/components/header/index.js @@ -1,12 +1,14 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ import { PostSavedState, PostPreviewButton } from '@wordpress/editor'; import { useSelect } from '@wordpress/data'; -import { - PinnedItems, - __experimentalMainDashboardButton as MainDashboardButton, -} from '@wordpress/interface'; +import { PinnedItems } from '@wordpress/interface'; import { useViewportMatch } from '@wordpress/compose'; /** @@ -17,6 +19,8 @@ import HeaderToolbar from './header-toolbar'; import MoreMenu from './more-menu'; import PostPublishButtonOrToggle from './post-publish-button-or-toggle'; import { default as DevicePreview } from '../device-preview'; +import MainDashboardButton from './main-dashboard-button'; +import TemplateSaveButton from './template-save-button'; function Header( { setEntitiesSavedStatesCallback } ) { const { @@ -25,6 +29,7 @@ function Header( { setEntitiesSavedStatesCallback } ) { isSaving, showIconLabels, hasReducedUI, + isEditingTemplate, } = useSelect( ( select ) => ( { hasActiveMetaboxes: select( 'core/edit-post' ).hasMetaBoxes(), @@ -38,14 +43,19 @@ function Header( { setEntitiesSavedStatesCallback } ) { hasReducedUI: select( 'core/edit-post' ).isFeatureActive( 'reducedUI' ), + isEditingTemplate: select( 'core/edit-post' ).isEditingTemplate(), } ), [] ); const isLargeViewport = useViewportMatch( 'large' ); + const classes = classnames( 'edit-post-header', { + 'has-reduced-ui': hasReducedUI, + } ); + return ( - <div className="edit-post-header"> + <div className={ classes }> <MainDashboardButton.Slot> <FullscreenModeClose /> </MainDashboardButton.Slot> @@ -53,7 +63,7 @@ function Header( { setEntitiesSavedStatesCallback } ) { <HeaderToolbar /> </div> <div className="edit-post-header__settings"> - { ! hasReducedUI && ( + { ! isEditingTemplate && ( <> { ! isPublishSidebarOpened && ( // This button isn't completely hidden by the publish sidebar. @@ -72,15 +82,16 @@ function Header( { setEntitiesSavedStatesCallback } ) { forceIsAutosaveable={ hasActiveMetaboxes } forcePreviewLink={ isSaving ? null : undefined } /> + <PostPublishButtonOrToggle + forceIsDirty={ hasActiveMetaboxes } + forceIsSaving={ isSaving } + setEntitiesSavedStatesCallback={ + setEntitiesSavedStatesCallback + } + /> </> ) } - <PostPublishButtonOrToggle - forceIsDirty={ hasActiveMetaboxes } - forceIsSaving={ isSaving } - setEntitiesSavedStatesCallback={ - setEntitiesSavedStatesCallback - } - /> + { isEditingTemplate && <TemplateSaveButton /> } { ( isLargeViewport || ! showIconLabels ) && ( <> <PinnedItems.Slot scope="core/edit-post" /> diff --git a/packages/interface/src/components/main-dashboard-button/index.js b/packages/edit-post/src/components/header/main-dashboard-button/index.js similarity index 100% rename from packages/interface/src/components/main-dashboard-button/index.js rename to packages/edit-post/src/components/header/main-dashboard-button/index.js diff --git a/packages/edit-post/src/components/header/mode-switcher/index.js b/packages/edit-post/src/components/header/mode-switcher/index.js index 5ee3ec93747978..fa32c71f6d3f3d 100644 --- a/packages/edit-post/src/components/header/mode-switcher/index.js +++ b/packages/edit-post/src/components/header/mode-switcher/index.js @@ -4,6 +4,7 @@ import { __ } from '@wordpress/i18n'; import { MenuItemsChoice, MenuGroup } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; /** * Set of available mode options. @@ -30,7 +31,7 @@ function ModeSwitcher() { } = useSelect( ( select ) => ( { shortcut: select( - 'core/keyboard-shortcuts' + keyboardShortcutsStore ).getShortcutRepresentation( 'core/edit-post/toggle-mode' ), isRichEditingEnabled: select( 'core/editor' ).getEditorSettings() .richEditingEnabled, diff --git a/packages/edit-post/src/components/header/template-save-button/index.js b/packages/edit-post/src/components/header/template-save-button/index.js new file mode 100644 index 00000000000000..48089eb55df50d --- /dev/null +++ b/packages/edit-post/src/components/header/template-save-button/index.js @@ -0,0 +1,49 @@ +/** + * WordPress dependencies + */ +import { EntitiesSavedStates } from '@wordpress/editor'; +import { Button } from '@wordpress/components'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { ActionsPanelFill } from '../../layout/actions-panel'; +import { store as editPostStore } from '../../../store'; + +function TemplateSaveButton() { + const [ + isEntitiesReviewPanelOpen, + setIsEntitiesReviewPanelOpen, + ] = useState( false ); + const { setIsEditingTemplate } = useDispatch( editPostStore ); + return ( + <> + <Button onClick={ () => setIsEditingTemplate( false ) } isTertiary> + { __( 'Cancel' ) } + </Button> + <Button + isPrimary + onClick={ () => setIsEntitiesReviewPanelOpen( true ) } + aria-expanded={ isEntitiesReviewPanelOpen } + > + { __( 'Apply' ) } + </Button> + <ActionsPanelFill> + <EntitiesSavedStates + isOpen={ isEntitiesReviewPanelOpen } + close={ ( entities ) => { + setIsEntitiesReviewPanelOpen( false ); + if ( entities?.length ) { + setIsEditingTemplate( false ); + } + } } + /> + </ActionsPanelFill> + </> + ); +} + +export default TemplateSaveButton; diff --git a/packages/edit-post/src/components/header/template-title/index.js b/packages/edit-post/src/components/header/template-title/index.js new file mode 100644 index 00000000000000..deb2b905ee9e34 --- /dev/null +++ b/packages/edit-post/src/components/header/template-title/index.js @@ -0,0 +1,43 @@ +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { store as editorStore } from '@wordpress/editor'; +import { store as coreStore } from '@wordpress/core-data'; + +/** + * Internal dependencies + */ +import { store as editPostStore } from '../../../store'; + +function TemplateTitle() { + const { template, isEditing } = useSelect( ( select ) => { + const { getEditedPostAttribute } = select( editorStore ); + const { __experimentalGetTemplateForLink } = select( coreStore ); + const { isEditingTemplate } = select( editPostStore ); + const link = getEditedPostAttribute( 'link' ); + const _isEditing = isEditingTemplate(); + return { + template: _isEditing + ? __experimentalGetTemplateForLink( link ) + : null, + isEditing: _isEditing, + }; + }, [] ); + + if ( ! isEditing || ! template ) { + return null; + } + + return ( + <span className="edit-post-template-title"> + { + /* translators: 1: Template name. */ + sprintf( __( 'Editing template: %s' ), template.slug ) + } + </span> + ); +} + +export default TemplateTitle; diff --git a/packages/edit-post/src/components/header/template-title/style.scss b/packages/edit-post/src/components/header/template-title/style.scss new file mode 100644 index 00000000000000..b4d6132c089757 --- /dev/null +++ b/packages/edit-post/src/components/header/template-title/style.scss @@ -0,0 +1,5 @@ +.edit-post-template-title { + display: inline-flex; + flex-grow: 1; + justify-content: center; +} diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/dynamic-shortcut.js b/packages/edit-post/src/components/keyboard-shortcut-help-modal/dynamic-shortcut.js index f60e0f2aad846d..fe97fba37e14ac 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/dynamic-shortcut.js +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/dynamic-shortcut.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { useSelect } from '@wordpress/data'; +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; /** * Internal dependencies @@ -14,7 +15,7 @@ function DynamicShortcut( { name } ) { getShortcutKeyCombination, getShortcutDescription, getShortcutAliases, - } = select( 'core/keyboard-shortcuts' ); + } = select( keyboardShortcutsStore ); return { keyCombination: getShortcutKeyCombination( name ), diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js b/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js index ebabc14ca3b747..9c269c4a1be751 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js @@ -9,7 +9,10 @@ import { isString } from 'lodash'; */ import { Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useShortcut } from '@wordpress/keyboard-shortcuts'; +import { + useShortcut, + store as keyboardShortcutsStore, +} from '@wordpress/keyboard-shortcuts'; import { withSelect, withDispatch, useSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; @@ -71,7 +74,7 @@ const ShortcutCategorySection = ( { } ) => { const categoryShortcuts = useSelect( ( select ) => { - return select( 'core/keyboard-shortcuts' ).getCategoryShortcuts( + return select( keyboardShortcutsStore ).getCategoryShortcuts( categoryName ); }, diff --git a/packages/edit-post/src/components/keyboard-shortcuts/index.js b/packages/edit-post/src/components/keyboard-shortcuts/index.js index 77ea945af679bd..65facebd21891a 100644 --- a/packages/edit-post/src/components/keyboard-shortcuts/index.js +++ b/packages/edit-post/src/components/keyboard-shortcuts/index.js @@ -3,7 +3,10 @@ */ import { useEffect } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; -import { useShortcut } from '@wordpress/keyboard-shortcuts'; +import { + useShortcut, + store as keyboardShortcutsStore, +} from '@wordpress/keyboard-shortcuts'; import { __ } from '@wordpress/i18n'; function KeyboardShortcuts() { @@ -32,7 +35,7 @@ function KeyboardShortcuts() { closeGeneralSidebar, toggleFeature, } = useDispatch( 'core/edit-post' ); - const { registerShortcut } = useDispatch( 'core/keyboard-shortcuts' ); + const { registerShortcut } = useDispatch( keyboardShortcutsStore ); useEffect( () => { registerShortcut( { diff --git a/packages/edit-post/src/components/layout/actions-panel.js b/packages/edit-post/src/components/layout/actions-panel.js index 5ac15a9c253c91..e1ad347a4bf7d7 100644 --- a/packages/edit-post/src/components/layout/actions-panel.js +++ b/packages/edit-post/src/components/layout/actions-panel.js @@ -3,7 +3,7 @@ */ import { EntitiesSavedStates, PostPublishPanel } from '@wordpress/editor'; import { useSelect, useDispatch } from '@wordpress/data'; -import { Button } from '@wordpress/components'; +import { Button, createSlotFill } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useCallback } from '@wordpress/element'; /** @@ -12,6 +12,10 @@ import { useCallback } from '@wordpress/element'; import PluginPostPublishPanel from '../sidebar/plugin-post-publish-panel'; import PluginPrePublishPanel from '../sidebar/plugin-pre-publish-panel'; +const { Fill, Slot } = createSlotFill( 'ActionsPanel' ); + +export const ActionsPanelFill = Fill; + export default function ActionsPanel( { setEntitiesSavedStatesCallback, closeEntitiesSavedStates, @@ -92,6 +96,7 @@ export default function ActionsPanel( { isOpen={ isEntitiesSavedStatesOpen } close={ closeEntitiesSavedStates } /> + <Slot bubblesVirtually /> { ! isEntitiesSavedStatesOpen && unmountableContent } </> ); diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index f7b2384e787b76..013c9d18daccba 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -17,14 +17,18 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { BlockBreadcrumb, __experimentalLibrary as Library, + __unstableUseEditorStyles as useEditorStyles, } from '@wordpress/block-editor'; import { Button, ScrollLock, Popover, - FocusReturnProvider, + __unstableUseDrop as useDrop, } from '@wordpress/components'; -import { useViewportMatch } from '@wordpress/compose'; +import { + useViewportMatch, + __experimentalUseDialog as useDialog, +} from '@wordpress/compose'; import { PluginArea } from '@wordpress/plugins'; import { __ } from '@wordpress/i18n'; import { @@ -32,8 +36,9 @@ import { FullscreenMode, InterfaceSkeleton, } from '@wordpress/interface'; -import { useState, useEffect, useCallback } from '@wordpress/element'; +import { useState, useEffect, useCallback, useRef } from '@wordpress/element'; import { close } from '@wordpress/icons'; +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; /** * Internal dependencies @@ -50,10 +55,9 @@ import SettingsSidebar from '../sidebar/settings-sidebar'; import MetaBoxes from '../meta-boxes'; import WelcomeGuide from '../welcome-guide'; import ActionsPanel from './actions-panel'; -import PopoverWrapper from './popover-wrapper'; const interfaceLabels = { - leftSidebar: __( 'Block library' ), + secondarySidebar: __( 'Block library' ), /* translators: accessibility text for the editor top bar landmark region. */ header: __( 'Editor top bar' ), /* translators: accessibility text for the editor content landmark region. */ @@ -66,7 +70,7 @@ const interfaceLabels = { footer: __( 'Editor footer' ), }; -function Layout() { +function Layout( { settings } ) { const isMobileViewport = useViewportMatch( 'medium', '<' ); const isHugeViewport = useViewportMatch( 'huge', '>=' ); const { @@ -110,12 +114,12 @@ function Layout() { .richEditingEnabled, hasActiveMetaboxes: select( 'core/edit-post' ).hasMetaBoxes(), previousShortcut: select( - 'core/keyboard-shortcuts' + keyboardShortcutsStore ).getAllShortcutRawKeyCombinations( 'core/edit-post/previous-region' ), nextShortcut: select( - 'core/keyboard-shortcuts' + keyboardShortcutsStore ).getAllShortcutRawKeyCombinations( 'core/edit-post/next-region' ), showIconLabels: select( 'core/edit-post' ).isFeatureActive( 'showIconLabels' @@ -149,7 +153,7 @@ function Layout() { }, [ isInserterOpened, isHugeViewport ] ); // Local state for save panel. - // Note 'thruthy' callback implies an open panel. + // Note 'truthy' callback implies an open panel. const [ entitiesSavedStatesCallback, setEntitiesSavedStatesCallback, @@ -163,6 +167,13 @@ function Layout() { }, [ entitiesSavedStatesCallback ] ); + const ref = useRef(); + + useDrop( ref ); + useEditorStyles( ref, settings.styles ); + const [ inserterDialogRef, inserterDialogProps ] = useDialog( { + onClose: () => setIsInserterOpened( false ), + } ); return ( <> @@ -174,128 +185,118 @@ function Layout() { <EditPostKeyboardShortcuts /> <EditorKeyboardShortcutsRegister /> <SettingsSidebar /> - <FocusReturnProvider> - <InterfaceSkeleton - className={ className } - labels={ interfaceLabels } - header={ - <Header - setEntitiesSavedStatesCallback={ - setEntitiesSavedStatesCallback - } - /> - } - leftSidebar={ - mode === 'visual' && - isInserterOpened && ( - <PopoverWrapper - className="edit-post-layout__inserter-panel-popover-wrapper" - onClose={ () => setIsInserterOpened( false ) } - > - <div className="edit-post-layout__inserter-panel"> - <div className="edit-post-layout__inserter-panel-header"> - <Button - icon={ close } - onClick={ () => - setIsInserterOpened( false ) - } - /> - </div> - <div className="edit-post-layout__inserter-panel-content"> - <Library - showMostUsedBlocks={ - showMostUsedBlocks - } - showInserterHelpPanel - onSelect={ () => { - if ( isMobileViewport ) { - setIsInserterOpened( - false - ); - } - } } - /> - </div> - </div> - </PopoverWrapper> - ) - } - sidebar={ - ( ! isMobileViewport || sidebarIsOpened ) && ( - <> - { ! isMobileViewport && ! sidebarIsOpened && ( - <div className="edit-post-layout__toogle-sidebar-panel"> - <Button - isSecondary - className="edit-post-layout__toogle-sidebar-panel-button" - onClick={ openSidebarPanel } - aria-expanded={ false } - > - { hasBlockSelected - ? __( 'Open block settings' ) - : __( - 'Open document settings' - ) } - </Button> - </div> - ) } - <ComplementaryArea.Slot scope="core/edit-post" /> - </> - ) - } - content={ - <> - <EditorNotices /> - { ( mode === 'text' || ! isRichEditingEnabled ) && ( - <TextEditor /> - ) } - { isRichEditingEnabled && mode === 'visual' && ( - <VisualEditor /> - ) } - <div className="edit-post-layout__metaboxes"> - <MetaBoxes location="normal" /> - <MetaBoxes location="advanced" /> + <InterfaceSkeleton + ref={ ref } + className={ className } + labels={ interfaceLabels } + header={ + <Header + setEntitiesSavedStatesCallback={ + setEntitiesSavedStatesCallback + } + /> + } + secondarySidebar={ + mode === 'visual' && + isInserterOpened && ( + <div + ref={ inserterDialogRef } + { ...inserterDialogProps } + className="edit-post-layout__inserter-panel" + > + <div className="edit-post-layout__inserter-panel-header"> + <Button + icon={ close } + onClick={ () => + setIsInserterOpened( false ) + } + /> + </div> + <div className="edit-post-layout__inserter-panel-content"> + <Library + showMostUsedBlocks={ showMostUsedBlocks } + showInserterHelpPanel + onSelect={ () => { + if ( isMobileViewport ) { + setIsInserterOpened( false ); + } + } } + /> </div> - { isMobileViewport && sidebarIsOpened && ( - <ScrollLock /> + </div> + ) + } + sidebar={ + ( ! isMobileViewport || sidebarIsOpened ) && ( + <> + { ! isMobileViewport && ! sidebarIsOpened && ( + <div className="edit-post-layout__toogle-sidebar-panel"> + <Button + isSecondary + className="edit-post-layout__toogle-sidebar-panel-button" + onClick={ openSidebarPanel } + aria-expanded={ false } + > + { hasBlockSelected + ? __( 'Open block settings' ) + : __( 'Open document settings' ) } + </Button> + </div> ) } + <ComplementaryArea.Slot scope="core/edit-post" /> </> - } - footer={ - ! hasReducedUI && - ! isMobileViewport && - isRichEditingEnabled && - mode === 'visual' && ( - <div className="edit-post-layout__footer"> - <BlockBreadcrumb /> - </div> - ) - } - actions={ - <ActionsPanel - closeEntitiesSavedStates={ - closeEntitiesSavedStates - } - isEntitiesSavedStatesOpen={ - entitiesSavedStatesCallback - } - setEntitiesSavedStatesCallback={ - setEntitiesSavedStatesCallback - } - /> - } - shortcuts={ { - previous: previousShortcut, - next: nextShortcut, - } } - /> - <ManageBlocksModal /> - <PreferencesModal /> - <KeyboardShortcutHelpModal /> - <WelcomeGuide /> - <Popover.Slot /> - <PluginArea /> - </FocusReturnProvider> + ) + } + content={ + <> + <EditorNotices /> + { ( mode === 'text' || ! isRichEditingEnabled ) && ( + <TextEditor /> + ) } + { isRichEditingEnabled && mode === 'visual' && ( + <VisualEditor /> + ) } + <div className="edit-post-layout__metaboxes"> + <MetaBoxes location="normal" /> + <MetaBoxes location="advanced" /> + </div> + { isMobileViewport && sidebarIsOpened && ( + <ScrollLock /> + ) } + </> + } + footer={ + ! hasReducedUI && + ! isMobileViewport && + isRichEditingEnabled && + mode === 'visual' && ( + <div className="edit-post-layout__footer"> + <BlockBreadcrumb /> + </div> + ) + } + actions={ + <ActionsPanel + closeEntitiesSavedStates={ closeEntitiesSavedStates } + isEntitiesSavedStatesOpen={ + entitiesSavedStatesCallback + } + setEntitiesSavedStatesCallback={ + setEntitiesSavedStatesCallback + } + /> + } + shortcuts={ { + previous: previousShortcut, + next: nextShortcut, + } } + /> + <ManageBlocksModal /> + <PreferencesModal /> + <KeyboardShortcutHelpModal /> + <WelcomeGuide /> + <Popover.Slot /> + <PluginArea /> </> ); } diff --git a/packages/edit-post/src/components/layout/popover-wrapper.js b/packages/edit-post/src/components/layout/popover-wrapper.js deleted file mode 100644 index 375ba0ace1179d..00000000000000 --- a/packages/edit-post/src/components/layout/popover-wrapper.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * WordPress dependencies - */ -import { - withConstrainedTabbing, - withFocusReturn, - withFocusOutside, -} from '@wordpress/components'; -import { Component } from '@wordpress/element'; -import { ESCAPE } from '@wordpress/keycodes'; - -function stopPropagation( event ) { - event.stopPropagation(); -} - -const DetectOutside = withFocusOutside( - class extends Component { - handleFocusOutside( event ) { - this.props.onFocusOutside( event ); - } - - render() { - return this.props.children; - } - } -); - -const FocusManaged = withConstrainedTabbing( - withFocusReturn( ( { children } ) => children ) -); - -export default function PopoverWrapper( { onClose, children, className } ) { - // Event handlers - const maybeClose = ( event ) => { - // Close on escape - if ( event.keyCode === ESCAPE && onClose ) { - event.stopPropagation(); - onClose(); - } - }; - - // Disable reason: this stops certain events from propagating outside of the component. - // - onMouseDown is disabled as this can cause interactions with other DOM elements - /* eslint-disable jsx-a11y/no-static-element-interactions */ - return ( - <div - className={ className } - onKeyDown={ maybeClose } - onMouseDown={ stopPropagation } - > - <DetectOutside onFocusOutside={ onClose }> - <FocusManaged>{ children }</FocusManaged> - </DetectOutside> - </div> - ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ -} diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index 96f8c1d3befe45..dfb1dc7fc4c2ae 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -45,7 +45,7 @@ } // Keep it open on focus to avoid conflict with navigate-regions animation. - .is-focusing-regions & { + [role="region"]:focus & { transform: translateX(0%); } } @@ -95,17 +95,6 @@ background-color: $gray-400; } -// Ideally we don't need all these nested divs. -// Removing these requires a refactoring of the different a11y HoCs. -.edit-post-layout__inserter-panel-popover-wrapper { - &, - & > div, - & > div > div, - & > div > div > div { - height: 100%; - } -} - .edit-post-layout__inserter-panel { height: 100%; display: flex; diff --git a/packages/edit-post/src/components/manage-blocks-modal/manager.js b/packages/edit-post/src/components/manage-blocks-modal/manager.js index f5922f8549bd44..85c2af7917c775 100644 --- a/packages/edit-post/src/components/manage-blocks-modal/manager.js +++ b/packages/edit-post/src/components/manage-blocks-modal/manager.js @@ -6,6 +6,7 @@ import { filter, includes, isArray } from 'lodash'; /** * WordPress dependencies */ +import { store as blocksStore } from '@wordpress/blocks'; import { withSelect } from '@wordpress/data'; import { compose, withState } from '@wordpress/compose'; import { TextControl } from '@wordpress/components'; @@ -102,7 +103,7 @@ export default compose( [ getCategories, hasBlockSupport, isMatchingSearchTerm, - } = select( 'core/blocks' ); + } = select( blocksStore ); const { getPreference } = select( 'core/edit-post' ); const hiddenBlockTypes = getPreference( 'hiddenBlockTypes' ); const numberOfHiddenBlocks = diff --git a/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js b/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js index 27a1d7733f1b80..d6193b0448979e 100644 --- a/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js @@ -4,6 +4,7 @@ import { ComplementaryArea } from '@wordpress/interface'; import { useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; /** * Renders a sidebar when activated. The contents within the `PluginSidebar` will appear as content within the sidebar. @@ -78,7 +79,7 @@ export default function PluginSidebarEditPost( { className, ...props } ) { 'title' ), shortcut: select( - 'core/keyboard-shortcuts' + keyboardShortcutsStore ).getShortcutRepresentation( 'core/edit-post/toggle-sidebar' ), showIconLabels: select( 'core/edit-post' ).isFeatureActive( 'showIconLabels' diff --git a/packages/edit-post/src/components/sidebar/post-status/index.js b/packages/edit-post/src/components/sidebar/post-status/index.js index 11311e91849c2b..ad2437e4c1f1a0 100644 --- a/packages/edit-post/src/components/sidebar/post-status/index.js +++ b/packages/edit-post/src/components/sidebar/post-status/index.js @@ -18,6 +18,7 @@ import PostSlug from '../post-slug'; import PostFormat from '../post-format'; import PostPendingStatus from '../post-pending-status'; import PluginPostStatusInfo from '../plugin-post-status-info'; +import PostTemplate from '../post-template'; /** * Module Constants @@ -35,6 +36,7 @@ function PostStatus( { isOpened, onTogglePanel } ) { <PluginPostStatusInfo.Slot> { ( fills ) => ( <> + <PostTemplate /> <PostVisibility /> <PostSchedule /> <PostFormat /> diff --git a/packages/edit-post/src/components/sidebar/post-template/index.js b/packages/edit-post/src/components/sidebar/post-template/index.js new file mode 100644 index 00000000000000..9c3380860aacbf --- /dev/null +++ b/packages/edit-post/src/components/sidebar/post-template/index.js @@ -0,0 +1,91 @@ +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; +import { PanelRow, Button } from '@wordpress/components'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { createInterpolateElement } from '@wordpress/element'; +import { store as editorStore } from '@wordpress/editor'; +import { store as coreStore } from '@wordpress/core-data'; +import { store as noticesStore } from '@wordpress/notices'; + +/** + * Internal dependencies + */ +import { store as editPostStore } from '../../../store'; + +function PostTemplate() { + const { template, isEditing, isFSETheme } = useSelect( ( select ) => { + const { + getEditedPostAttribute, + __unstableIsAutodraftPost, + getCurrentPostType, + } = select( editorStore ); + const { __experimentalGetTemplateForLink } = select( coreStore ); + const { isEditingTemplate } = select( editPostStore ); + const link = getEditedPostAttribute( 'link' ); + const isFSEEnabled = select( editorStore ).getEditorSettings() + .isFSETheme; + return { + template: + isFSEEnabled && + link && + ! __unstableIsAutodraftPost() && + getCurrentPostType() !== 'wp_template' + ? __experimentalGetTemplateForLink( link ) + : null, + isEditing: isEditingTemplate(), + isFSETheme: isFSEEnabled, + }; + }, [] ); + const { setIsEditingTemplate } = useDispatch( editPostStore ); + const { createSuccessNotice } = useDispatch( noticesStore ); + + if ( ! isFSETheme || ! template ) { + return null; + } + + return ( + <PanelRow className="edit-post-post-template"> + <span>{ __( 'Template' ) }</span> + { ! isEditing && ( + <span className="edit-post-post-template__value"> + { createInterpolateElement( + sprintf( + /* translators: 1: Template name. */ + __( '%s (<a>Edit</a>)' ), + template.slug + ), + { + a: ( + <Button + isLink + onClick={ () => { + setIsEditingTemplate( true ); + createSuccessNotice( + __( + 'Editing template. Changes made here affect all posts and pages that use the template.' + ), + { + type: 'snackbar', + } + ); + } } + > + { __( 'Edit' ) } + </Button> + ), + } + ) } + </span> + ) } + { isEditing && ( + <span className="edit-post-post-template__value"> + { template.slug } + </span> + ) } + </PanelRow> + ); +} + +export default PostTemplate; diff --git a/packages/edit-post/src/components/sidebar/post-template/style.scss b/packages/edit-post/src/components/sidebar/post-template/style.scss new file mode 100644 index 00000000000000..d625e897224368 --- /dev/null +++ b/packages/edit-post/src/components/sidebar/post-template/style.scss @@ -0,0 +1,13 @@ +.edit-post-post-template { + width: 100%; + justify-content: left; + + span { + display: block; + width: 45%; + } +} + +.edit-post-post-template__value { + padding-left: 6px; +} diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js index c4548ba61820b3..d488179edc7184 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js @@ -4,6 +4,7 @@ import { BlockInspector } from '@wordpress/block-editor'; import { cog } from '@wordpress/icons'; import { Platform } from '@wordpress/element'; +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; /** * Internal dependencies @@ -49,7 +50,7 @@ const SettingsSidebar = () => { sidebar = 'edit-post/document'; } const shortcut = select( - 'core/keyboard-shortcuts' + keyboardShortcutsStore ).getShortcutRepresentation( 'core/edit-post/toggle-sidebar' ); return { sidebarName: sidebar, keyboardShortcut: shortcut }; }, [] ); diff --git a/packages/edit-post/src/components/visual-editor/block-inspector-button.js b/packages/edit-post/src/components/visual-editor/block-inspector-button.js index 5a57dcd63e5ae7..2eb90256b4c17d 100644 --- a/packages/edit-post/src/components/visual-editor/block-inspector-button.js +++ b/packages/edit-post/src/components/visual-editor/block-inspector-button.js @@ -10,12 +10,13 @@ import { __ } from '@wordpress/i18n'; import { MenuItem } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { speak } from '@wordpress/a11y'; +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; export function BlockInspectorButton( { onClick = noop, small = false } ) { const { shortcut, areAdvancedSettingsOpened } = useSelect( ( select ) => ( { shortcut: select( - 'core/keyboard-shortcuts' + keyboardShortcutsStore ).getShortcutRepresentation( 'core/edit-post/toggle-sidebar' ), areAdvancedSettingsOpened: select( 'core/edit-post' ).getActiveGeneralSidebarName() === diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js index 026acf6654bb51..0f6d4b73d94f34 100644 --- a/packages/edit-post/src/components/visual-editor/index.js +++ b/packages/edit-post/src/components/visual-editor/index.js @@ -2,21 +2,23 @@ * WordPress dependencies */ import { - PostTitle, VisualEditorGlobalKeyboardShortcuts, + PostTitle, } from '@wordpress/editor'; import { WritingFlow, - Typewriter, - ObserveTyping, BlockList, - CopyHandler, - BlockSelectionClearer, - MultiSelectScrollIntoView, + __unstableUseBlockSelectionClearer as useBlockSelectionClearer, + __unstableUseTypewriter as useTypewriter, + __unstableUseClipboardHandler as useClipboardHandler, + __unstableUseTypingObserver as useTypingObserver, + __unstableUseScrollMultiSelectionIntoView as useScrollMultiSelectionIntoView, __experimentalBlockSettingsMenuFirstItem, __experimentalUseResizeCanvas as useResizeCanvas, + __unstableUseCanvasClickRedirect as useCanvasClickRedirect, } from '@wordpress/block-editor'; import { Popover } from '@wordpress/components'; +import { useRef } from '@wordpress/element'; /** * Internal dependencies @@ -24,40 +26,60 @@ import { Popover } from '@wordpress/components'; import BlockInspectorButton from './block-inspector-button'; import { useSelect } from '@wordpress/data'; -function VisualEditor() { - const deviceType = useSelect( ( select ) => { - return select( 'core/edit-post' ).__experimentalGetPreviewDeviceType(); +export default function VisualEditor() { + const ref = useRef(); + const { deviceType, isTemplateMode } = useSelect( ( select ) => { + const { + isEditingTemplate, + __experimentalGetPreviewDeviceType, + } = select( 'core/edit-post' ); + return { + deviceType: __experimentalGetPreviewDeviceType(), + isTemplateMode: isEditingTemplate(), + }; }, [] ); + const hasMetaBoxes = useSelect( + ( select ) => select( 'core/edit-post' ).hasMetaBoxes(), + [] + ); + const desktopCanvasStyles = { + height: '100%', + // Add a constant padding for the typewritter effect. When typing at the + // bottom, there needs to be room to scroll up. + paddingBottom: hasMetaBoxes ? null : '40vh', + }; + const resizedCanvasStyles = useResizeCanvas( deviceType ); - const inlineStyles = useResizeCanvas( deviceType ); + useScrollMultiSelectionIntoView( ref ); + useBlockSelectionClearer( ref ); + useTypewriter( ref ); + useClipboardHandler( ref ); + useTypingObserver( ref ); + useCanvasClickRedirect( ref ); return ( - <BlockSelectionClearer - className="edit-post-visual-editor editor-styles-wrapper" - style={ inlineStyles } - > + <div className="edit-post-visual-editor"> <VisualEditorGlobalKeyboardShortcuts /> - <MultiSelectScrollIntoView /> <Popover.Slot name="block-toolbar" /> - <Typewriter> - <CopyHandler> - <WritingFlow> - <ObserveTyping> - <div className="edit-post-visual-editor__post-title-wrapper"> - <PostTitle /> - </div> - <BlockList /> - </ObserveTyping> - </WritingFlow> - </CopyHandler> - </Typewriter> + <div + ref={ ref } + className="editor-styles-wrapper" + style={ resizedCanvasStyles || desktopCanvasStyles } + > + <WritingFlow> + { ! isTemplateMode && ( + <div className="edit-post-visual-editor__post-title-wrapper"> + <PostTitle /> + </div> + ) } + <BlockList /> + </WritingFlow> + </div> <__experimentalBlockSettingsMenuFirstItem> { ( { onClose } ) => ( <BlockInspectorButton onClick={ onClose } /> ) } </__experimentalBlockSettingsMenuFirstItem> - </BlockSelectionClearer> + </div> ); } - -export default VisualEditor; diff --git a/packages/edit-post/src/components/visual-editor/style.scss b/packages/edit-post/src/components/visual-editor/style.scss index a9a261a417eb8c..6894839d78b0b5 100644 --- a/packages/edit-post/src/components/visual-editor/style.scss +++ b/packages/edit-post/src/components/visual-editor/style.scss @@ -1,8 +1,5 @@ .edit-post-visual-editor { position: relative; - padding-top: 50px; - // Default background color so that grey .edit-post-editor-regions__content color doesn't show through. - background-color: $white; // The button element easily inherits styles that are meant for the editor style. // These rules enhance the specificity to reduce that inheritance. @@ -28,22 +25,16 @@ } } -.edit-post-visual-editor > .block-editor__typewriter, -.edit-post-visual-editor > .block-editor__typewriter > div, -.edit-post-visual-editor > .block-editor__typewriter > div > .block-editor-writing-flow, -.edit-post-visual-editor > .block-editor__typewriter > div > .block-editor-writing-flow > .block-editor-writing-flow__click-redirect { - height: 100%; -} +.editor-styles-wrapper { + // Default background color so that grey .edit-post-editor-regions__content + // color doesn't show through. + background-color: $white; -.edit-post-visual-editor .block-editor-writing-flow__click-redirect { - // Allow the page to be scrolled with the last block in the middle. - min-height: 40vh; - width: 100%; -} + cursor: text; -// Hide the extra space when there are metaboxes. -.has-metaboxes .edit-post-visual-editor .block-editor-writing-flow__click-redirect { - height: 0; + > * { + cursor: auto; + } } // Ideally this wrapper div is not needed but if we want to match the positioning of blocks @@ -61,6 +52,6 @@ // Apply default block margin below the post title. // This ensures the first block on the page is in a good position. // This rule can be retired once the title becomes an actual block. - margin-bottom: ($block-padding * 2) + $block-spacing; // This matches 2em in the vanilla style. + margin-bottom: $default-block-margin; } } diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index d7b01fb351f623..898adf8652f0a9 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -1,25 +1,24 @@ /** * External dependencies */ -import memize from 'memize'; import { size, map, without, omit } from 'lodash'; /** * WordPress dependencies */ -import { withSelect, withDispatch } from '@wordpress/data'; +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect, useDispatch } from '@wordpress/data'; import { EditorProvider, ErrorBoundary, PostLockedModal, } from '@wordpress/editor'; -import { StrictMode, Component } from '@wordpress/element'; +import { StrictMode, useMemo } from '@wordpress/element'; import { KeyboardShortcuts, SlotFillProvider, - DropZoneProvider, + __unstableDropZoneContextProvider as DropZoneContextProvider, } from '@wordpress/components'; -import { compose } from '@wordpress/compose'; /** * Internal dependencies @@ -29,30 +28,78 @@ import Layout from './components/layout'; import EditorInitialization from './components/editor-initialization'; import EditPostSettings from './components/edit-post-settings'; -class Editor extends Component { - constructor() { - super( ...arguments ); - - this.getEditorSettings = memize( this.getEditorSettings, { - maxSize: 1, - } ); - } - - getEditorSettings( - settings, +function Editor( { + postId, + postType, + settings, + initialEdits, + onError, + ...props +} ) { + const { hasFixedToolbar, focusMode, hasReducedUI, hasThemeStyles, + post, + preferredStyleVariations, hiddenBlockTypes, blockTypes, - preferredStyleVariations, __experimentalLocalAutosaveInterval, - __experimentalSetIsInserterOpened, - updatePreferredStyleVariations, - keepCaretInsideBlock - ) { - settings = { + keepCaretInsideBlock, + isTemplateMode, + template, + } = useSelect( ( select ) => { + const { + isFeatureActive, + getPreference, + __experimentalGetPreviewDeviceType, + isEditingTemplate, + } = select( 'core/edit-post' ); + const { getEntityRecord, __experimentalGetTemplateForLink } = select( + 'core' + ); + const { getEditorSettings, __unstableIsAutodraftPost } = select( + 'core/editor' + ); + const { getBlockTypes } = select( blocksStore ); + const postObject = getEntityRecord( 'postType', postType, postId ); + const isFSETheme = getEditorSettings().isFSETheme; + + return { + hasFixedToolbar: + isFeatureActive( 'fixedToolbar' ) || + __experimentalGetPreviewDeviceType() !== 'Desktop', + focusMode: isFeatureActive( 'focusMode' ), + hasReducedUI: isFeatureActive( 'reducedUI' ), + hasThemeStyles: isFeatureActive( 'themeStyles' ), + preferredStyleVariations: getPreference( + 'preferredStyleVariations' + ), + hiddenBlockTypes: getPreference( 'hiddenBlockTypes' ), + blockTypes: getBlockTypes(), + __experimentalLocalAutosaveInterval: getPreference( + 'localAutosaveInterval' + ), + keepCaretInsideBlock: isFeatureActive( 'keepCaretInsideBlock' ), + isTemplateMode: isEditingTemplate(), + template: + isFSETheme && + postObject && + ! __unstableIsAutodraftPost() && + postType !== 'wp_template' + ? __experimentalGetTemplateForLink( postObject.link ) + : null, + post: postObject, + }; + } ); + + const { updatePreferredStyleVariations, setIsInserterOpened } = useDispatch( + 'core/edit-post' + ); + + const editorSettings = useMemo( () => { + const result = { ...( hasThemeStyles ? settings : omit( settings, [ 'defaultEditorStyles' ] ) ), @@ -66,7 +113,7 @@ class Editor extends Component { __experimentalLocalAutosaveInterval, // This is marked as experimental to give time for the quick inserter to mature. - __experimentalSetIsInserterOpened, + __experimentalSetIsInserterOpened: setIsInserterOpened, keepCaretInsideBlock, styles: hasThemeStyles ? settings.styles @@ -83,121 +130,61 @@ class Editor extends Component { ? map( blockTypes, 'name' ) : settings.allowedBlockTypes || []; - settings.allowedBlockTypes = without( + result.allowedBlockTypes = without( defaultAllowedBlockTypes, ...hiddenBlockTypes ); } - return settings; - } - - render() { - const { - settings, - hasFixedToolbar, - focusMode, - hasReducedUI, - hasThemeStyles, - post, - postId, - initialEdits, - onError, - hiddenBlockTypes, - blockTypes, - preferredStyleVariations, - __experimentalLocalAutosaveInterval, - setIsInserterOpened, - updatePreferredStyleVariations, - keepCaretInsideBlock, - ...props - } = this.props; - - if ( ! post ) { - return null; - } - - const editorSettings = this.getEditorSettings( - settings, - hasFixedToolbar, - focusMode, - hasReducedUI, - hasThemeStyles, - hiddenBlockTypes, - blockTypes, - preferredStyleVariations, - __experimentalLocalAutosaveInterval, - setIsInserterOpened, - updatePreferredStyleVariations, - keepCaretInsideBlock - ); + return result; + }, [ + settings, + hasFixedToolbar, + focusMode, + hasReducedUI, + hasThemeStyles, + hiddenBlockTypes, + blockTypes, + preferredStyleVariations, + __experimentalLocalAutosaveInterval, + setIsInserterOpened, + updatePreferredStyleVariations, + keepCaretInsideBlock, + ] ); - return ( - <StrictMode> - <EditPostSettings.Provider value={ settings }> - <SlotFillProvider> - <DropZoneProvider> - <EditorProvider - settings={ editorSettings } - post={ post } - initialEdits={ initialEdits } - useSubRegistry={ false } - { ...props } - > - <ErrorBoundary onError={ onError }> - <EditorInitialization postId={ postId } /> - <Layout /> - <KeyboardShortcuts - shortcuts={ preventEventDiscovery } - /> - </ErrorBoundary> - <PostLockedModal /> - </EditorProvider> - </DropZoneProvider> - </SlotFillProvider> - </EditPostSettings.Provider> - </StrictMode> - ); + if ( ! post ) { + return null; } -} -export default compose( [ - withSelect( ( select, { postId, postType } ) => { - const { - isFeatureActive, - getPreference, - __experimentalGetPreviewDeviceType, - } = select( 'core/edit-post' ); - const { getEntityRecord } = select( 'core' ); - const { getBlockTypes } = select( 'core/blocks' ); + return ( + <StrictMode> + <EditPostSettings.Provider value={ settings }> + <SlotFillProvider> + <DropZoneContextProvider> + <EditorProvider + settings={ editorSettings } + post={ post } + initialEdits={ initialEdits } + useSubRegistry={ false } + __unstableTemplate={ + isTemplateMode ? template : undefined + } + { ...props } + > + <ErrorBoundary onError={ onError }> + <EditorInitialization postId={ postId } /> + <Layout settings={ settings } /> + <KeyboardShortcuts + shortcuts={ preventEventDiscovery } + /> + </ErrorBoundary> + <PostLockedModal /> + </EditorProvider> + </DropZoneContextProvider> + </SlotFillProvider> + </EditPostSettings.Provider> + </StrictMode> + ); +} - return { - hasFixedToolbar: - isFeatureActive( 'fixedToolbar' ) || - __experimentalGetPreviewDeviceType() !== 'Desktop', - focusMode: isFeatureActive( 'focusMode' ), - hasReducedUI: isFeatureActive( 'reducedUI' ), - hasThemeStyles: isFeatureActive( 'themeStyles' ), - post: getEntityRecord( 'postType', postType, postId ), - preferredStyleVariations: getPreference( - 'preferredStyleVariations' - ), - hiddenBlockTypes: getPreference( 'hiddenBlockTypes' ), - blockTypes: getBlockTypes(), - __experimentalLocalAutosaveInterval: getPreference( - 'localAutosaveInterval' - ), - keepCaretInsideBlock: isFeatureActive( 'keepCaretInsideBlock' ), - }; - } ), - withDispatch( ( dispatch ) => { - const { - updatePreferredStyleVariations, - setIsInserterOpened, - } = dispatch( 'core/edit-post' ); - return { - updatePreferredStyleVariations, - setIsInserterOpened, - }; - } ), -] )( Editor ); +export default Editor; diff --git a/packages/edit-post/src/editor.native.js b/packages/edit-post/src/editor.native.js index b227410684135c..2e7c227aa321f4 100644 --- a/packages/edit-post/src/editor.native.js +++ b/packages/edit-post/src/editor.native.js @@ -10,7 +10,12 @@ import { I18nManager } from 'react-native'; */ import { Component } from '@wordpress/element'; import { EditorProvider } from '@wordpress/editor'; -import { parse, serialize, rawHandler } from '@wordpress/blocks'; +import { + parse, + serialize, + rawHandler, + store as blocksStore, +} from '@wordpress/blocks'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { subscribeSetFocusOnTitle } from '@wordpress/react-native-bridge'; @@ -43,9 +48,7 @@ class Editor extends Component { hasFixedToolbar, focusMode, hiddenBlockTypes, - blockTypes, - colors, - gradients + blockTypes ) { settings = { ...settings, @@ -56,6 +59,11 @@ class Editor extends Component { // Omit hidden block types if exists and non-empty. if ( size( hiddenBlockTypes ) > 0 ) { + if ( settings.allowedBlockTypes === undefined ) { + // if no specific flags for allowedBlockTypes are set, assume `true` + // meaning allow all block types + settings.allowedBlockTypes = true; + } // Defer to passed setting for `allowedBlockTypes` if provided as // anything other than `true` (where `true` is equivalent to allow // all block types). @@ -70,14 +78,6 @@ class Editor extends Component { ); } - if ( colors !== undefined ) { - settings.colors = colors; - } - - if ( gradients !== undefined ) { - settings.gradients = gradients; - } - return settings; } @@ -119,8 +119,6 @@ class Editor extends Component { post, postId, postType, - colors, - gradients, initialHtml, editorMode, ...props @@ -131,9 +129,7 @@ class Editor extends Component { hasFixedToolbar, focusMode, hiddenBlockTypes, - blockTypes, - colors, - gradients + blockTypes ); const normalizedPost = post || { @@ -176,7 +172,7 @@ export default compose( [ getPreference, __experimentalGetPreviewDeviceType, } = select( 'core/edit-post' ); - const { getBlockTypes } = select( 'core/blocks' ); + const { getBlockTypes } = select( blocksStore ); return { hasFixedToolbar: @@ -190,7 +186,6 @@ export default compose( [ } ), withDispatch( ( dispatch ) => { const { switchEditorMode } = dispatch( 'core/edit-post' ); - return { switchEditorMode, }; diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index f93ece6ce7f236..19e4c48a246814 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -4,10 +4,6 @@ import '@wordpress/core-data'; import '@wordpress/block-editor'; import '@wordpress/editor'; -import '@wordpress/keyboard-shortcuts'; -import '@wordpress/reusable-blocks'; -import '@wordpress/viewport'; -import '@wordpress/notices'; import { registerCoreBlocks, __experimentalRegisterExperimentalCoreBlocks, @@ -97,7 +93,9 @@ export function initializeEditor( ); registerCoreBlocks(); if ( process.env.GUTENBERG_PHASE === 2 ) { - __experimentalRegisterExperimentalCoreBlocks( settings ); + __experimentalRegisterExperimentalCoreBlocks( + settings.__unstableEnableFullSiteEditingBlocks + ); } // Show a console log warning if the browser is not in Standards rendering mode. @@ -161,3 +159,4 @@ export { default as PluginPrePublishPanel } from './components/sidebar/plugin-pr export { default as PluginSidebar } from './components/sidebar/plugin-sidebar'; export { default as PluginSidebarMoreMenuItem } from './components/header/plugin-sidebar-more-menu-item'; export { default as __experimentalFullscreenModeClose } from './components/header/fullscreen-mode-close'; +export { default as __experimentalMainDashboardButton } from './components/header/main-dashboard-button'; diff --git a/packages/edit-post/src/index.native.js b/packages/edit-post/src/index.native.js index 93502e3701d091..70b0924f967fdb 100644 --- a/packages/edit-post/src/index.native.js +++ b/packages/edit-post/src/index.native.js @@ -2,10 +2,7 @@ * WordPress dependencies */ import '@wordpress/core-data'; -import '@wordpress/viewport'; -import '@wordpress/notices'; import '@wordpress/format-library'; -import '@wordpress/reusable-blocks'; import { render } from '@wordpress/element'; /** diff --git a/packages/edit-post/src/plugins/copy-content-menu-item/index.js b/packages/edit-post/src/plugins/copy-content-menu-item/index.js index a070a9f75079d5..01198d4bd57709 100644 --- a/packages/edit-post/src/plugins/copy-content-menu-item/index.js +++ b/packages/edit-post/src/plugins/copy-content-menu-item/index.js @@ -6,6 +6,7 @@ import { withDispatch, withSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { useCopyOnClick, compose, ifCondition } from '@wordpress/compose'; import { useRef, useEffect } from '@wordpress/element'; +import { store as noticesStore } from '@wordpress/notices'; function CopyContentMenuItem( { createNotice, editedPostContent } ) { const ref = useRef(); @@ -36,7 +37,7 @@ export default compose( ), } ) ), withDispatch( ( dispatch ) => { - const { createNotice } = dispatch( 'core/notices' ); + const { createNotice } = dispatch( noticesStore ); return { createNotice, diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index df9123616c2cf2..95901295d0ca3b 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -1,12 +1,20 @@ /** * External dependencies */ -import { castArray } from 'lodash'; +import { castArray, reduce } from 'lodash'; /** * WordPress dependencies */ -import { dispatch } from '@wordpress/data-controls'; +import { __ } from '@wordpress/i18n'; +import { apiFetch } from '@wordpress/data-controls'; +import { controls, dispatch, select, subscribe } from '@wordpress/data'; +import { speak } from '@wordpress/a11y'; + +/** + * Internal dependencies + */ +import { getMetaBoxContainer } from '../utils/meta-boxes'; /** * Returns an action object used in signalling that the user opened an editor sidebar. @@ -16,7 +24,7 @@ import { dispatch } from '@wordpress/data-controls'; * @yield {Object} Action object. */ export function* openGeneralSidebar( name ) { - yield dispatch( + yield controls.dispatch( 'core/interface', 'enableComplementaryArea', 'core/edit-post', @@ -30,7 +38,7 @@ export function* openGeneralSidebar( name ) { * @yield {Object} Action object. */ export function* closeGeneralSidebar() { - yield dispatch( + yield controls.dispatch( 'core/interface', 'disableComplementaryArea', 'core/edit-post' @@ -153,11 +161,22 @@ export function toggleFeature( feature ) { }; } -export function switchEditorMode( mode ) { - return { +export function* switchEditorMode( mode ) { + yield { type: 'SWITCH_MODE', mode, }; + + // Unselect blocks when we switch to the code editor. + if ( mode !== 'visual' ) { + yield controls.dispatch( 'core/block-editor', 'clearSelectedBlock' ); + } + + const message = + mode === 'visual' + ? __( 'Visual editor selected' ) + : __( 'Code editor selected' ); + speak( message, 'assertive' ); } /** @@ -234,30 +253,136 @@ export function showBlockTypes( blockNames ) { }; } +let saveMetaboxUnsubscribe; + /** * Returns an action object used in signaling * what Meta boxes are available in which location. * * @param {Object} metaBoxesPerLocation Meta boxes per location. * - * @return {Object} Action object. + * @yield {Object} Action object. */ -export function setAvailableMetaBoxesPerLocation( metaBoxesPerLocation ) { - return { +export function* setAvailableMetaBoxesPerLocation( metaBoxesPerLocation ) { + yield { type: 'SET_META_BOXES_PER_LOCATIONS', metaBoxesPerLocation, }; + + const postType = yield controls.select( + 'core/editor', + 'getCurrentPostType' + ); + if ( window.postboxes.page !== postType ) { + window.postboxes.add_postbox_toggles( postType ); + } + + let wasSavingPost = yield controls.select( 'core/editor', 'isSavingPost' ); + let wasAutosavingPost = yield controls.select( + 'core/editor', + 'isAutosavingPost' + ); + + // Meta boxes are initialized once at page load. It is not necessary to + // account for updates on each state change. + // + // See: https://github.com/WordPress/WordPress/blob/5.1.1/wp-admin/includes/post.php#L2307-L2309 + const hasActiveMetaBoxes = yield controls.select( + 'core/edit-post', + 'hasMetaBoxes' + ); + + // First remove any existing subscription in order to prevent multiple saves + if ( !! saveMetaboxUnsubscribe ) { + saveMetaboxUnsubscribe(); + } + + // Save metaboxes when performing a full save on the post. + saveMetaboxUnsubscribe = subscribe( () => { + const isSavingPost = select( 'core/editor' ).isSavingPost(); + const isAutosavingPost = select( 'core/editor' ).isAutosavingPost(); + + // Save metaboxes on save completion, except for autosaves that are not a post preview. + const shouldTriggerMetaboxesSave = + hasActiveMetaBoxes && + wasSavingPost && + ! isSavingPost && + ! wasAutosavingPost; + + // Save current state for next inspection. + wasSavingPost = isSavingPost; + wasAutosavingPost = isAutosavingPost; + + if ( shouldTriggerMetaboxesSave ) { + dispatch( 'core/edit-post' ).requestMetaBoxUpdates(); + } + } ); } /** * Returns an action object used to request meta box update. * - * @return {Object} Action object. + * @yield {Object} Action object. */ -export function requestMetaBoxUpdates() { - return { +export function* requestMetaBoxUpdates() { + yield { type: 'REQUEST_META_BOX_UPDATES', }; + + // Saves the wp_editor fields + if ( window.tinyMCE ) { + window.tinyMCE.triggerSave(); + } + + // Additional data needed for backward compatibility. + // If we do not provide this data, the post will be overridden with the default values. + const post = yield controls.select( 'core/editor', 'getCurrentPost' ); + const additionalData = [ + post.comment_status ? [ 'comment_status', post.comment_status ] : false, + post.ping_status ? [ 'ping_status', post.ping_status ] : false, + post.sticky ? [ 'sticky', post.sticky ] : false, + post.author ? [ 'post_author', post.author ] : false, + ].filter( Boolean ); + + // We gather all the metaboxes locations data and the base form data + const baseFormData = new window.FormData( + document.querySelector( '.metabox-base-form' ) + ); + const activeMetaBoxLocations = yield controls.select( + 'core/edit-post', + 'getActiveMetaBoxLocations' + ); + const formDataToMerge = [ + baseFormData, + ...activeMetaBoxLocations.map( + ( location ) => + new window.FormData( getMetaBoxContainer( location ) ) + ), + ]; + + // Merge all form data objects into a single one. + const formData = reduce( + formDataToMerge, + ( memo, currentFormData ) => { + for ( const [ key, value ] of currentFormData ) { + memo.append( key, value ); + } + return memo; + }, + new window.FormData() + ); + additionalData.forEach( ( [ key, value ] ) => + formData.append( key, value ) + ); + + // Save the metaboxes + yield apiFetch( { + url: window._wpMetaBoxUrl, + method: 'POST', + body: formData, + parse: false, + } ); + yield controls.dispatch( 'core/edit-post', 'metaBoxUpdatesSuccess' ); } /** @@ -297,3 +422,16 @@ export function setIsInserterOpened( value ) { value, }; } + +/** + * Returns an action object used to switch to template editing. + * + * @param {boolean} value Is editing template. + * @return {Object} Action object. + */ +export function setIsEditingTemplate( value ) { + return { + type: 'SET_IS_EDITING_TEMPLATE', + value, + }; +} diff --git a/packages/edit-post/src/store/constants.js b/packages/edit-post/src/store/constants.js index d3c02c71f312ee..115401a8f46639 100644 --- a/packages/edit-post/src/store/constants.js +++ b/packages/edit-post/src/store/constants.js @@ -3,7 +3,7 @@ * * @type {string} */ -export const STORE_KEY = 'core/edit-post'; +export const STORE_NAME = 'core/edit-post'; /** * CSS selector string for the admin bar view post link anchor tag. diff --git a/packages/edit-post/src/store/controls.js b/packages/edit-post/src/store/controls.js deleted file mode 100644 index 3a64e045f69666..00000000000000 --- a/packages/edit-post/src/store/controls.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * WordPress dependencies - */ -import { createRegistryControl } from '@wordpress/data'; - -/** - * Calls a selector using the current state. - * - * @param {string} storeName Store name. - * @param {string} selectorName Selector name. - * @param {Array} args Selector arguments. - * - * @return {Object} control descriptor. - */ -export function select( storeName, selectorName, ...args ) { - return { - type: 'SELECT', - storeName, - selectorName, - args, - }; -} - -const controls = { - SELECT: createRegistryControl( - ( registry ) => ( { storeName, selectorName, args } ) => { - return registry.select( storeName )[ selectorName ]( ...args ); - } - ), -}; - -export default controls; diff --git a/packages/edit-post/src/store/effects.js b/packages/edit-post/src/store/effects.js deleted file mode 100644 index 0e69bd005dd6f9..00000000000000 --- a/packages/edit-post/src/store/effects.js +++ /dev/null @@ -1,141 +0,0 @@ -/** - * External dependencies - */ -import { reduce } from 'lodash'; - -/** - * WordPress dependencies - */ -import { select, subscribe, dispatch } from '@wordpress/data'; -import { speak } from '@wordpress/a11y'; -import { __ } from '@wordpress/i18n'; -import apiFetch from '@wordpress/api-fetch'; - -/** - * Internal dependencies - */ -import { metaBoxUpdatesSuccess, requestMetaBoxUpdates } from './actions'; -import { getActiveMetaBoxLocations } from './selectors'; -import { getMetaBoxContainer } from '../utils/meta-boxes'; - -let saveMetaboxUnsubscribe; - -const effects = { - SET_META_BOXES_PER_LOCATIONS( action, store ) { - // Allow toggling metaboxes panels - // We need to wait for all scripts to load - // If the meta box loads the post script, it will already trigger this. - // After merge in Core, make sure to drop the timeout and update the postboxes script - // to avoid the double binding. - setTimeout( () => { - const postType = select( 'core/editor' ).getCurrentPostType(); - if ( window.postboxes.page !== postType ) { - window.postboxes.add_postbox_toggles( postType ); - } - } ); - - let wasSavingPost = select( 'core/editor' ).isSavingPost(); - let wasAutosavingPost = select( 'core/editor' ).isAutosavingPost(); - - // Meta boxes are initialized once at page load. It is not necessary to - // account for updates on each state change. - // - // See: https://github.com/WordPress/WordPress/blob/5.1.1/wp-admin/includes/post.php#L2307-L2309 - const hasActiveMetaBoxes = select( 'core/edit-post' ).hasMetaBoxes(); - - // First remove any existing subscription in order to prevent multiple saves - if ( !! saveMetaboxUnsubscribe ) { - saveMetaboxUnsubscribe(); - } - - // Save metaboxes when performing a full save on the post. - saveMetaboxUnsubscribe = subscribe( () => { - const isSavingPost = select( 'core/editor' ).isSavingPost(); - const isAutosavingPost = select( 'core/editor' ).isAutosavingPost(); - - // Save metaboxes on save completion, except for autosaves that are not a post preview. - const shouldTriggerMetaboxesSave = - hasActiveMetaBoxes && - wasSavingPost && - ! isSavingPost && - ! wasAutosavingPost; - - // Save current state for next inspection. - wasSavingPost = isSavingPost; - wasAutosavingPost = isAutosavingPost; - - if ( shouldTriggerMetaboxesSave ) { - store.dispatch( requestMetaBoxUpdates() ); - } - } ); - }, - REQUEST_META_BOX_UPDATES( action, store ) { - // Saves the wp_editor fields - if ( window.tinyMCE ) { - window.tinyMCE.triggerSave(); - } - - const state = store.getState(); - - // Additional data needed for backward compatibility. - // If we do not provide this data, the post will be overridden with the default values. - const post = select( 'core/editor' ).getCurrentPost( state ); - const additionalData = [ - post.comment_status - ? [ 'comment_status', post.comment_status ] - : false, - post.ping_status ? [ 'ping_status', post.ping_status ] : false, - post.sticky ? [ 'sticky', post.sticky ] : false, - post.author ? [ 'post_author', post.author ] : false, - ].filter( Boolean ); - - // We gather all the metaboxes locations data and the base form data - const baseFormData = new window.FormData( - document.querySelector( '.metabox-base-form' ) - ); - const formDataToMerge = [ - baseFormData, - ...getActiveMetaBoxLocations( state ).map( - ( location ) => - new window.FormData( getMetaBoxContainer( location ) ) - ), - ]; - - // Merge all form data objects into a single one. - const formData = reduce( - formDataToMerge, - ( memo, currentFormData ) => { - for ( const [ key, value ] of currentFormData ) { - memo.append( key, value ); - } - return memo; - }, - new window.FormData() - ); - additionalData.forEach( ( [ key, value ] ) => - formData.append( key, value ) - ); - - // Save the metaboxes - apiFetch( { - url: window._wpMetaBoxUrl, - method: 'POST', - body: formData, - parse: false, - } ).then( () => store.dispatch( metaBoxUpdatesSuccess() ) ); - }, - SWITCH_MODE( action ) { - // Unselect blocks when we switch to the code editor. - if ( action.mode !== 'visual' ) { - dispatch( 'core/block-editor' ).clearSelectedBlock(); - } - - const message = - action.mode === 'visual' - ? __( 'Visual editor selected' ) - : __( 'Code editor selected' ); - speak( message, 'assertive' ); - }, -}; - -export default effects; diff --git a/packages/edit-post/src/store/index.js b/packages/edit-post/src/store/index.js index de84aaf3062ab0..c461e54c601fc6 100644 --- a/packages/edit-post/src/store/index.js +++ b/packages/edit-post/src/store/index.js @@ -1,30 +1,33 @@ /** * WordPress dependencies */ -import { registerStore } from '@wordpress/data'; -import { controls as dataControls } from '@wordpress/data-controls'; +import { createReduxStore, registerStore } from '@wordpress/data'; +import { controls } from '@wordpress/data-controls'; /** * Internal dependencies */ import reducer from './reducer'; -import applyMiddlewares from './middlewares'; import * as actions from './actions'; import * as selectors from './selectors'; -import controls from './controls'; -import { STORE_KEY } from './constants'; +import { STORE_NAME } from './constants'; -const store = registerStore( STORE_KEY, { +const storeConfig = { reducer, actions, selectors, - controls: { - ...dataControls, - ...controls, - }, + controls, persist: [ 'preferences' ], -} ); +}; -applyMiddlewares( store ); +/** + * Store definition for the edit post namespace. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore + * + * @type {Object} + */ +export const store = createReduxStore( STORE_NAME, storeConfig ); -export default store; +// Ideally we use register instead of register store. +registerStore( STORE_NAME, storeConfig ); diff --git a/packages/edit-post/src/store/middlewares.js b/packages/edit-post/src/store/middlewares.js deleted file mode 100644 index 06cacf026d58c2..00000000000000 --- a/packages/edit-post/src/store/middlewares.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * External dependencies - */ -import { flowRight } from 'lodash'; -import refx from 'refx'; - -/** - * Internal dependencies - */ -import effects from './effects'; - -/** - * Applies the custom middlewares used specifically in the editor module. - * - * @param {Object} store Store Object. - * - * @return {Object} Update Store Object. - */ -function applyMiddlewares( store ) { - const middlewares = [ refx( effects ) ]; - - let enhancedDispatch = () => { - throw new Error( - 'Dispatching while constructing your middleware is not allowed. ' + - 'Other middleware would not be applied to this dispatch.' - ); - }; - let chain = []; - - const middlewareAPI = { - getState: store.getState, - dispatch: ( ...args ) => enhancedDispatch( ...args ), - }; - chain = middlewares.map( ( middleware ) => middleware( middlewareAPI ) ); - enhancedDispatch = flowRight( ...chain )( store.dispatch ); - - store.dispatch = enhancedDispatch; - return store; -} - -export default applyMiddlewares; diff --git a/packages/edit-post/src/store/reducer.js b/packages/edit-post/src/store/reducer.js index 934de97aa73c08..1f1e762f728135 100644 --- a/packages/edit-post/src/store/reducer.js +++ b/packages/edit-post/src/store/reducer.js @@ -252,6 +252,20 @@ function isInserterOpened( state = false, action ) { return state; } +/** + * Reducer tracking whether the inserter is open. + * + * @param {boolean} state + * @param {Object} action + */ +function isEditingTemplate( state = false, action ) { + switch ( action.type ) { + case 'SET_IS_EDITING_TEMPLATE': + return action.value; + } + return state; +} + const metaBoxes = combineReducers( { isSaving: isSavingMetaBoxes, locations: metaBoxLocations, @@ -265,4 +279,5 @@ export default combineReducers( { removedPanels, deviceType, isInserterOpened, + isEditingTemplate, } ); diff --git a/packages/edit-post/src/store/selectors.js b/packages/edit-post/src/store/selectors.js index 68dcc83671ed00..69d1ded82cf568 100644 --- a/packages/edit-post/src/store/selectors.js +++ b/packages/edit-post/src/store/selectors.js @@ -324,3 +324,14 @@ export function __experimentalGetPreviewDeviceType( state ) { export function isInserterOpened( state ) { return state.isInserterOpened; } + +/** + * Returns true if the template editing mode is enabled. + * + * @param {Object} state Global application state. + * + * @return {boolean} Whether we're editing the template. + */ +export function isEditingTemplate( state ) { + return state.isEditingTemplate; +} diff --git a/packages/edit-post/src/store/test/actions.js b/packages/edit-post/src/store/test/actions.js index 31877f378bb286..fc398c3aeb2117 100644 --- a/packages/edit-post/src/store/test/actions.js +++ b/packages/edit-post/src/store/test/actions.js @@ -1,3 +1,8 @@ +/** + * WordPress dependencies + */ +import { controls } from '@wordpress/data'; + /** * Internal dependencies */ @@ -95,9 +100,17 @@ describe( 'actions', () => { } ); describe( 'requestMetaBoxUpdates', () => { - it( 'should return the REQUEST_META_BOX_UPDATES action', () => { - expect( requestMetaBoxUpdates() ).toEqual( { - type: 'REQUEST_META_BOX_UPDATES', + it( 'should yield the REQUEST_META_BOX_UPDATES action', () => { + const fulfillment = requestMetaBoxUpdates(); + expect( fulfillment.next() ).toEqual( { + done: false, + value: { + type: 'REQUEST_META_BOX_UPDATES', + }, + } ); + expect( fulfillment.next() ).toEqual( { + done: false, + value: controls.select( 'core/editor', 'getCurrentPost' ), } ); } ); } ); diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss index c49b21a71c6901..f902e64e5848ba 100644 --- a/packages/edit-post/src/style.scss +++ b/packages/edit-post/src/style.scss @@ -3,6 +3,7 @@ @import "./components/header/fullscreen-mode-close/style.scss"; @import "./components/header/header-toolbar/style.scss"; @import "./components/header/more-menu/style.scss"; +@import "./components/header/template-title/style.scss"; @import "./components/keyboard-shortcut-help-modal/style.scss"; @import "./components/layout/style.scss"; @import "./components/manage-blocks-modal/style.scss"; @@ -15,6 +16,7 @@ @import "./components/sidebar/post-schedule/style.scss"; @import "./components/sidebar/post-slug/style.scss"; @import "./components/sidebar/post-status/style.scss"; +@import "./components/sidebar/post-template/style.scss"; @import "./components/sidebar/post-visibility/style.scss"; @import "./components/sidebar/settings-header/style.scss"; @import "./components/text-editor/style.scss"; @@ -68,12 +70,12 @@ body.block-editor-page { right: 0; bottom: 0; left: 0; - min-height: calc(100vh - #{ $admin-bar-height-big }); + min-height: calc(100vh - #{$admin-bar-height-big}); } // The WP header height changes at this breakpoint. @include break-medium { - min-height: calc(100vh - #{ $admin-bar-height }); + min-height: calc(100vh - #{$admin-bar-height}); body.is-fullscreen-mode & { min-height: 100vh; @@ -88,24 +90,7 @@ body.block-editor-page { iframe { width: 100%; } - - .components-navigate-regions { - height: 100%; - } -} - - -// These are default block editor styles in case the theme doesn't provide them. -.wp-block { - max-width: $content-width; - - &[data-align="wide"] { - max-width: 1100px; - } - - &[data-align="full"] { - max-width: none; - } } +@include default-block-widths(); @include wordpress-admin-schemes(); diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 3955f8e6458470..9b68e7bf01a80b 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-site", - "version": "1.15.4", + "version": "1.15.5", "description": "Edit Site Page module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -22,12 +22,8 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", - "sideEffects": [ - "build-style/**", - "!((src|build|build-module)/components/**)" - ], "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/a11y": "file:../a11y", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/block-editor": "file:../block-editor", diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js index 0041ce021870b9..61313977e33361 100644 --- a/packages/edit-site/src/components/block-editor/index.js +++ b/packages/edit-site/src/components/block-editor/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; -import { useCallback } from '@wordpress/element'; +import { useCallback, useRef } from '@wordpress/element'; import { useEntityBlockEditor } from '@wordpress/core-data'; import { BlockEditorProvider, @@ -12,11 +12,13 @@ import { WritingFlow, ObserveTyping, BlockList, + __unstableUseBlockSelectionClearer as useBlockSelectionClearer, } from '@wordpress/block-editor'; /** * Internal dependencies */ +import TemplatePartConverter from '../template-part-converter'; import NavigateToLink from '../navigate-to-link'; import { SidebarInspectorFill } from '../sidebar'; @@ -38,8 +40,11 @@ export default function BlockEditor( { setIsInserterOpen } ) { 'postType', templateType ); - const { setPage } = useDispatch( 'core/edit-site' ); + const ref = useRef(); + + useBlockSelectionClearer( ref ); + return ( <BlockEditorProvider settings={ settings } @@ -49,6 +54,7 @@ export default function BlockEditor( { setIsInserterOpen } ) { useSubRegistry={ false } > <BlockEditorKeyboardShortcuts /> + <TemplatePartConverter /> <__experimentalLinkControl.ViewerFill> { useCallback( ( fillProps ) => ( @@ -64,7 +70,10 @@ export default function BlockEditor( { setIsInserterOpen } ) { <SidebarInspectorFill> <BlockInspector /> </SidebarInspectorFill> - <div className="editor-styles-wrapper edit-site-block-editor__editor-styles-wrapper"> + <div + ref={ ref } + className="editor-styles-wrapper edit-site-block-editor__editor-styles-wrapper" + > <WritingFlow> <ObserveTyping> <BlockList className="edit-site-block-editor__block-list" /> diff --git a/packages/edit-site/src/components/editor/global-styles-provider.js b/packages/edit-site/src/components/editor/global-styles-provider.js index 0a1ef42b0026fd..db62624c076775 100644 --- a/packages/edit-site/src/components/editor/global-styles-provider.js +++ b/packages/edit-site/src/components/editor/global-styles-provider.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { set, get, mapValues } from 'lodash'; +import { set, get, mapValues, mergeWith } from 'lodash'; /** * WordPress dependencies @@ -13,17 +13,24 @@ import { useEffect, useMemo, } from '@wordpress/element'; +import { + __EXPERIMENTAL_STYLE_PROPERTY as STYLE_PROPERTY, + store as blocksStore, +} from '@wordpress/blocks'; import { useEntityProp } from '@wordpress/core-data'; -import { __EXPERIMENTAL_STYLE_PROPERTY as STYLE_PROPERTY } from '@wordpress/blocks'; import { useSelect, useDispatch } from '@wordpress/data'; /** * Internal dependencies */ import { - default as getGlobalStyles, - mergeTrees, -} from './global-styles-renderer'; + GLOBAL_CONTEXT_NAME, + GLOBAL_CONTEXT_SELECTOR, + GLOBAL_CONTEXT_SUPPORTS, + getValueFromVariable, + getPresetVariable, +} from './utils'; +import getGlobalStyles from './global-styles-renderer'; const EMPTY_CONTENT = '{}'; @@ -31,12 +38,21 @@ const GlobalStylesContext = createContext( { /* eslint-disable no-unused-vars */ getSetting: ( context, path ) => {}, setSetting: ( context, path, newValue ) => {}, - getStyleProperty: ( context, propertyName ) => {}, + getStyleProperty: ( context, propertyName, origin ) => {}, setStyleProperty: ( context, propertyName, newValue ) => {}, - globalContext: {}, + contexts: {}, /* eslint-enable no-unused-vars */ } ); +const mergeTreesCustomizer = ( objValue, srcValue ) => { + // We only pass as arrays the presets, + // in which case we want the new array of values + // to override the old array (no merging). + if ( Array.isArray( srcValue ) ) { + return srcValue; + } +}; + export const useGlobalStylesContext = () => useContext( GlobalStylesContext ); const useGlobalStylesEntityContent = () => { @@ -52,18 +68,84 @@ export const useGlobalStylesReset = () => { ]; }; -export default function GlobalStylesProvider( { - children, - baseStyles, - contexts, -} ) { +const extractSupportKeys = ( supports ) => { + const supportKeys = []; + Object.keys( STYLE_PROPERTY ).forEach( ( name ) => { + if ( get( supports, STYLE_PROPERTY[ name ].support, false ) ) { + supportKeys.push( name ); + } + } ); + return supportKeys; +}; + +const getContexts = ( blockTypes ) => { + const result = { + [ GLOBAL_CONTEXT_NAME ]: { + selector: GLOBAL_CONTEXT_SELECTOR, + supports: GLOBAL_CONTEXT_SUPPORTS, + }, + }; + + // Add contexts from block metadata. + blockTypes.forEach( ( blockType ) => { + const blockName = blockType.name; + const blockSelector = blockType?.supports?.__experimentalSelector; + const supports = extractSupportKeys( blockType?.supports ); + const hasSupport = supports.length > 0; + + if ( hasSupport && typeof blockSelector === 'string' ) { + result[ blockName ] = { + selector: blockSelector, + supports, + blockName, + }; + } else if ( hasSupport && typeof blockSelector === 'object' ) { + Object.keys( blockSelector ).forEach( ( key ) => { + result[ key ] = { + selector: blockSelector[ key ].selector, + supports, + blockName, + title: blockSelector[ key ].title, + attributes: blockSelector[ key ].attributes, + }; + } ); + } else if ( hasSupport ) { + const suffix = blockName.replace( 'core/', '' ).replace( '/', '-' ); + result[ blockName ] = { + selector: '.wp-block-' + suffix, + supports, + blockName, + }; + } + } ); + + return result; +}; + +export default function GlobalStylesProvider( { children, baseStyles } ) { const [ content, setContent ] = useGlobalStylesEntityContent(); + const { blockTypes, settings } = useSelect( ( select ) => { + return { + blockTypes: select( blocksStore ).getBlockTypes(), + settings: select( 'core/edit-site' ).getSettings(), + }; + } ); + const { updateSettings } = useDispatch( 'core/edit-site' ); + + const contexts = useMemo( () => getContexts( blockTypes ), [ blockTypes ] ); const { userStyles, mergedStyles } = useMemo( () => { - const parsedContent = content ? JSON.parse( content ) : {}; + const newUserStyles = content ? JSON.parse( content ) : {}; + const newMergedStyles = mergeWith( + {}, + baseStyles, + newUserStyles, + mergeTreesCustomizer + ); + return { - userStyles: parsedContent, - mergedStyles: mergeTrees( baseStyles, parsedContent ), + userStyles: newUserStyles, + mergedStyles: newMergedStyles, }; }, [ content ] ); @@ -82,11 +164,15 @@ export default function GlobalStylesProvider( { set( contextSettings, path, newValue ); setContent( JSON.stringify( newContent ) ); }, - getStyleProperty: ( context, propertyName ) => - get( - userStyles?.[ context ]?.styles, - STYLE_PROPERTY[ propertyName ] - ), + getStyleProperty: ( context, propertyName, origin = 'merged' ) => { + const styles = 'user' === origin ? userStyles : mergedStyles; + + const value = get( + styles?.[ context ]?.styles, + STYLE_PROPERTY[ propertyName ].value + ); + return getValueFromVariable( mergedStyles, context, value ); + }, setStyleProperty: ( context, propertyName, newValue ) => { const newContent = { ...userStyles }; let contextStyles = newContent?.[ context ]?.styles; @@ -94,50 +180,56 @@ export default function GlobalStylesProvider( { contextStyles = {}; set( newContent, [ context, 'styles' ], contextStyles ); } - set( contextStyles, STYLE_PROPERTY[ propertyName ], newValue ); + set( + contextStyles, + STYLE_PROPERTY[ propertyName ].value, + getPresetVariable( + mergedStyles, + context, + propertyName, + newValue + ) + ); setContent( JSON.stringify( newContent ) ); }, } ), - [ contexts, content ] + [ content, mergedStyles ] ); useEffect( () => { - if ( - typeof contexts !== 'object' || - typeof baseStyles !== 'object' || - typeof userStyles !== 'object' - ) { - return; - } - - const embeddedStylesheetId = 'global-styles-inline-css'; - let styleNode = document.getElementById( embeddedStylesheetId ); - - if ( ! styleNode ) { - styleNode = document.createElement( 'style' ); - styleNode.id = embeddedStylesheetId; - document - .getElementsByTagName( 'head' )[ 0 ] - .appendChild( styleNode ); - } - - styleNode.innerText = getGlobalStyles( contexts, mergedStyles ); - }, [ contexts, baseStyles, content ] ); - - const settings = useSelect( ( select ) => - select( 'core/edit-site' ).getSettings() - ); - const { updateSettings } = useDispatch( 'core/edit-site' ); - - useEffect( () => { + const newStyles = settings.styles.filter( + ( style ) => ! style.isGlobalStyles + ); updateSettings( { ...settings, + styles: [ + ...newStyles, + { + css: getGlobalStyles( + contexts, + mergedStyles, + STYLE_PROPERTY, + 'cssVariables' + ), + isGlobalStyles: true, + __experimentalNoWrapper: true, + }, + { + css: getGlobalStyles( + contexts, + mergedStyles, + STYLE_PROPERTY, + 'blockStyles' + ), + isGlobalStyles: true, + }, + ], __experimentalFeatures: mapValues( mergedStyles, - ( value ) => value.settings || {} + ( value ) => value?.settings || {} ), } ); - }, [ mergedStyles ] ); + }, [ contexts, mergedStyles ] ); return ( <GlobalStylesContext.Provider value={ nextValue }> diff --git a/packages/edit-site/src/components/editor/global-styles-renderer.js b/packages/edit-site/src/components/editor/global-styles-renderer.js index d17c9459249923..9fb1fb457d762f 100644 --- a/packages/edit-site/src/components/editor/global-styles-renderer.js +++ b/packages/edit-site/src/components/editor/global-styles-renderer.js @@ -1,168 +1,181 @@ /** * External dependencies */ -import { get, kebabCase, reduce } from 'lodash'; +import { get, kebabCase, reduce, startsWith } from 'lodash'; /** - * WordPress dependencies + * Internal dependencies */ -import { __EXPERIMENTAL_STYLE_PROPERTY as STYLE_PROPERTY } from '@wordpress/blocks'; +import { LINK_COLOR_DECLARATION, PRESET_METADATA } from './utils'; + +function compileStyleValue( uncompiledValue ) { + const VARIABLE_REFERENCE_PREFIX = 'var:'; + const VARIABLE_PATH_SEPARATOR_TOKEN_ATTRIBUTE = '|'; + const VARIABLE_PATH_SEPARATOR_TOKEN_STYLE = '--'; + if ( startsWith( uncompiledValue, VARIABLE_REFERENCE_PREFIX ) ) { + const variable = uncompiledValue + .slice( VARIABLE_REFERENCE_PREFIX.length ) + .split( VARIABLE_PATH_SEPARATOR_TOKEN_ATTRIBUTE ) + .join( VARIABLE_PATH_SEPARATOR_TOKEN_STYLE ); + return `var(--wp--${ variable })`; + } + return uncompiledValue; +} /** - * Internal dependencies + * Transform given preset tree into a set of style declarations. + * + * @param {Object} blockPresets + * + * @return {Array} An array of style declarations. */ -import { PRESET_CATEGORIES, LINK_COLOR_DECLARATION } from './utils'; - -export const mergeTrees = ( baseData, userData ) => { - // Deep clone from base data. - // - // We don't use cloneDeep from lodash here - // because we know the data is JSON compatible, - // see https://github.com/lodash/lodash/issues/1984 - const mergedTree = baseData ? JSON.parse( JSON.stringify( baseData ) ) : {}; +function getBlockPresetsDeclarations( blockPresets = {} ) { + return reduce( + PRESET_METADATA, + ( declarations, { path, valueKey, cssVarInfix } ) => { + const preset = get( blockPresets, path, [] ); + preset.forEach( ( value ) => { + declarations.push( + `--wp--preset--${ cssVarInfix }--${ value.slug }: ${ value[ valueKey ] }` + ); + } ); + return declarations; + }, + [] + ); +} - const styleKeys = [ 'typography', 'color' ]; - const settingKeys = [ 'typography', 'color', 'custom', 'spacing' ]; - Object.keys( userData ).forEach( ( context ) => { - styleKeys.forEach( ( key ) => { - // Normalize object shape. - if ( ! mergedTree[ context ].styles?.[ key ] ) { - mergedTree[ context ].styles[ key ] = {}; - } - // Merge base + user data. - mergedTree[ context ].styles[ key ] = { - ...mergedTree[ context ].styles[ key ], - ...userData[ context ]?.styles?.[ key ], - }; - } ); - settingKeys.forEach( ( key ) => { - // Normalize object shape. - if ( ! mergedTree[ context ].settings?.[ key ] ) { - mergedTree[ context ].settings[ key ] = {}; +/** + * Transform given preset tree into a set of preset class declarations. + * + * @param {string} blockSelector + * @param {Object} blockPresets + * @return {string} CSS declarations for the preset classes. + */ +function getBlockPresetClasses( blockSelector, blockPresets = {} ) { + return reduce( + PRESET_METADATA, + ( declarations, { path, valueKey, classes } ) => { + if ( ! classes ) { + return declarations; } - // Merge base + user data. - mergedTree[ context ].settings[ key ] = { - ...mergedTree[ context ].settings[ key ], - ...userData[ context ]?.settings?.[ key ], - }; - } ); + classes.forEach( ( { classSuffix, propertyName } ) => { + const presets = get( blockPresets, path, [] ); + presets.forEach( ( preset ) => { + const slug = preset.slug; + const value = preset[ valueKey ]; + const classSelectorToUse = `.has-${ slug }-${ classSuffix }`; + const selectorToUse = `${ blockSelector }${ classSelectorToUse }`; + declarations += `${ selectorToUse } {${ propertyName }: ${ value };}`; + } ); + } ); + return declarations; + }, + '' + ); +} + +function flattenTree( input = {}, prefix, token ) { + let result = []; + Object.keys( input ).forEach( ( key ) => { + const newKey = prefix + kebabCase( key.replace( '/', '-' ) ); + const newLeaf = input[ key ]; + + if ( newLeaf instanceof Object ) { + const newPrefix = newKey + token; + result = [ ...result, ...flattenTree( newLeaf, newPrefix, token ) ]; + } else { + result.push( `${ newKey }: ${ newLeaf }` ); + } } ); + return result; +} - return mergedTree; -}; - -export default ( blockData, tree ) => { - const styles = []; - // Can this be converted to a context, as the global context? - // See comment in the server. - styles.push( LINK_COLOR_DECLARATION ); - - /** - * Transform given style tree into a set of style declarations. - * - * @param {Object} blockSupports What styles the block supports. - * @param {Object} blockStyles Block styles. - * - * @return {Array} An array of style declarations. - */ - const getBlockStylesDeclarations = ( blockSupports, blockStyles ) => { - const declarations = []; - Object.keys( STYLE_PROPERTY ).forEach( ( key ) => { +/** + * Transform given style tree into a set of style declarations. + * + * @param {Object} blockSupports What styles the block supports. + * @param {Object} blockStyles Block styles. + * @param {Object} metadata Block styles metadata information. + * + * @return {Array} An array of style declarations. + */ +function getBlockStylesDeclarations( + blockSupports, + blockStyles = {}, + metadata +) { + return reduce( + metadata, + ( declarations, { value }, key ) => { const cssProperty = key.startsWith( '--' ) ? key : kebabCase( key ); if ( blockSupports.includes( key ) && - get( blockStyles, STYLE_PROPERTY[ key ], false ) + get( blockStyles, value, false ) ) { declarations.push( - `${ cssProperty }: ${ get( - blockStyles, - STYLE_PROPERTY[ key ] + `${ cssProperty }: ${ compileStyleValue( + get( blockStyles, value ) ) }` ); } - } ); - - return declarations; - }; + return declarations; + }, + [] + ); +} + +export default ( blockData, tree, metadata, type = 'all' ) => { + return reduce( + blockData, + ( styles, { selector }, context ) => { + if ( type === 'all' || type === 'cssVariables' ) { + const variableDeclarations = [ + ...getBlockPresetsDeclarations( tree?.[ context ] ), + ...flattenTree( + tree?.[ context ]?.settings?.custom, + '--wp--custom--', + '--' + ), + ]; - /** - * Transform given preset tree into a set of style declarations. - * - * @param {Object} blockPresets - * - * @return {Array} An array of style declarations. - */ - const getBlockPresetsDeclarations = ( blockPresets ) => { - return reduce( - PRESET_CATEGORIES, - ( declarations, { path, key }, category ) => { - const preset = get( blockPresets, path, [] ); - preset.forEach( ( value ) => { - declarations.push( - `--wp--preset--${ kebabCase( category ) }--${ - value.slug - }: ${ value[ key ] }` + if ( variableDeclarations.length > 0 ) { + styles.push( + `${ selector } { ${ variableDeclarations.join( + ';' + ) } }` ); - } ); - return declarations; - }, - [] - ); - }; - - const flattenTree = ( input, prefix, token ) => { - let result = []; - Object.keys( input ).forEach( ( key ) => { - const newKey = prefix + kebabCase( key.replace( '/', '-' ) ); - const newLeaf = input[ key ]; - - if ( newLeaf instanceof Object ) { - const newPrefix = newKey + token; - result = [ - ...result, - ...flattenTree( newLeaf, newPrefix, token ), - ]; - } else { - result.push( `${ newKey }: ${ newLeaf }` ); + } } - } ); - return result; - }; - - const getCustomDeclarations = ( blockCustom ) => { - if ( Object.keys( blockCustom ).length === 0 ) { - return []; - } - - return flattenTree( blockCustom, '--wp--custom--', '--' ); - }; - - const getBlockSelector = ( selector ) => { - // Can we hook into the styles generation mechanism - // so we can avoid having to increase the class specificity here - // and remap :root? - if ( ':root' === selector ) { - selector = ''; - } - return `.editor-styles-wrapper.editor-styles-wrapper ${ selector }`; - }; + if ( type === 'all' || type === 'blockStyles' ) { + const blockStyleDeclarations = getBlockStylesDeclarations( + blockData[ context ].supports, + tree?.[ context ]?.styles, + metadata + ); - Object.keys( blockData ).forEach( ( context ) => { - const blockSelector = getBlockSelector( blockData[ context ].selector ); - const blockDeclarations = [ - ...getBlockStylesDeclarations( - blockData[ context ].supports, - tree[ context ].styles - ), - ...getBlockPresetsDeclarations( tree[ context ].settings ), - ...getCustomDeclarations( tree[ context ].settings.custom ), - ]; - if ( blockDeclarations.length > 0 ) { - styles.push( - `${ blockSelector } { ${ blockDeclarations.join( ';' ) } }` - ); - } - } ); + if ( blockStyleDeclarations.length > 0 ) { + styles.push( + `${ selector } { ${ blockStyleDeclarations.join( + ';' + ) } }` + ); + } - return styles.join( '' ); + const presetClasses = getBlockPresetClasses( + selector, + tree?.[ context ] + ); + if ( presetClasses ) { + styles.push( presetClasses ); + } + } + return styles; + }, + // Can this be converted to a context, as the global context? + // See comment in the server. + type === 'all' || type === 'blockStyles' + ? [ LINK_COLOR_DECLARATION ] + : [] + ).join( '' ); }; diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 9d2bb47d17a905..bcd180fe66735c 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -1,22 +1,27 @@ /** * WordPress dependencies */ -import { useEffect, useState, useMemo, useCallback } from '@wordpress/element'; +import { + useEffect, + useState, + useMemo, + useCallback, + useRef, +} from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; import { SlotFillProvider, DropZoneProvider, Popover, - FocusReturnProvider, Button, } from '@wordpress/components'; import { EntityProvider } from '@wordpress/core-data'; import { BlockContextProvider, - BlockSelectionClearer, BlockBreadcrumb, - __unstableEditorStyles as EditorStyles, + __unstableUseEditorStyles as useEditorStyles, __experimentalUseResizeCanvas as useResizeCanvas, + __experimentalLibrary as Library, } from '@wordpress/block-editor'; import { FullscreenMode, @@ -26,6 +31,11 @@ import { import { EntitiesSavedStates, UnsavedChangesWarning } from '@wordpress/editor'; import { __ } from '@wordpress/i18n'; import { PluginArea } from '@wordpress/plugins'; +import { close } from '@wordpress/icons'; +import { + useViewportMatch, + __experimentalUseDialog as useDialog, +} from '@wordpress/compose'; /** * Internal dependencies @@ -36,17 +46,18 @@ import { SidebarComplementaryAreaFills } from '../sidebar'; import BlockEditor from '../block-editor'; import KeyboardShortcuts from '../keyboard-shortcuts'; import GlobalStylesProvider from './global-styles-provider'; -import LeftSidebar from '../left-sidebar'; import NavigationSidebar from '../navigation-sidebar'; +import URLQueryController from '../url-query-controller'; const interfaceLabels = { - leftSidebar: __( 'Block Library' ), + secondarySidebar: __( 'Block Library' ), drawer: __( 'Navigation Sidebar' ), }; function Editor() { const { isFullscreenActive, + isInserterOpen, deviceType, sidebarIsOpened, settings, @@ -54,11 +65,11 @@ function Editor() { templateType, page, template, - select, isNavigationOpen, - } = useSelect( ( _select ) => { + } = useSelect( ( select ) => { const { isFeatureActive, + isInserterOpened, __experimentalGetPreviewDeviceType, getSettings, getTemplateId, @@ -66,7 +77,7 @@ function Editor() { getTemplateType, getPage, isNavigationOpened, - } = _select( 'core/edit-site' ); + } = select( 'core/edit-site' ); const _templateId = getTemplateId(); const _templatePartId = getTemplatePartId(); const _templateType = getTemplateType(); @@ -79,29 +90,39 @@ function Editor() { } return { + isInserterOpen: isInserterOpened(), isFullscreenActive: isFeatureActive( 'fullscreenMode' ), deviceType: __experimentalGetPreviewDeviceType(), - sidebarIsOpened: !! _select( + sidebarIsOpened: !! select( 'core/interface' ).getActiveComplementaryArea( 'core/edit-site' ), settings: getSettings(), templateType: _templateType, page: getPage(), - template: _templateType - ? _select( 'core' ).getEntityRecord( - 'postType', - _templateType, - _entityId - ) - : null, - select: _select, + template: + _templateType && _entityId + ? select( 'core' ).getEntityRecord( + 'postType', + _templateType, + _entityId + ) + : null, entityId: _entityId, isNavigationOpen: isNavigationOpened(), }; }, [] ); - const { editEntityRecord } = useDispatch( 'core' ); + const { updateEditorSettings } = useDispatch( 'core/editor' ); const { setPage, setIsInserterOpened } = useDispatch( 'core/edit-site' ); + // Keep the defaultTemplateTypes in the core/editor settings too, + // so that they can be selected with core/editor selectors in any editor. + // This is needed because edit-site doesn't initialize with EditorProvider, + // which internally uses updateEditorSettings as well. + const { defaultTemplateTypes } = settings; + useEffect( () => { + updateEditorSettings( { defaultTemplateTypes } ); + }, [ defaultTemplateTypes ] ); + const inlineStyles = useResizeCanvas( deviceType ); const [ @@ -112,24 +133,9 @@ function Editor() { () => setIsEntitiesSavedStatesOpen( true ), [] ); - const closeEntitiesSavedStates = useCallback( - ( entitiesToSave ) => { - if ( entitiesToSave ) { - const { getEditedEntityRecord } = select( 'core' ); - entitiesToSave.forEach( ( { kind, name, key } ) => { - const record = getEditedEntityRecord( kind, name, key ); - - const edits = record.slug - ? { status: 'publish', title: record.slug } - : { status: 'publish' }; - - editEntityRecord( kind, name, key, edits ); - } ); - } - setIsEntitiesSavedStatesOpen( false ); - }, - [ select ] - ); + const closeEntitiesSavedStates = useCallback( () => { + setIsEntitiesSavedStatesOpen( false ); + }, [] ); // Set default query for misplaced Query Loop blocks, and // provide the root `queryContext` for top-level Query Loop @@ -164,9 +170,18 @@ function Editor() { } }, [ isNavigationOpen ] ); + const isMobile = useViewportMatch( 'medium', '<' ); + const ref = useRef(); + + useEditorStyles( ref, settings.styles ); + + const [ inserterDialogRef, inserterDialogProps ] = useDialog( { + onClose: () => setIsInserterOpened( false ), + } ); + return ( <> - <EditorStyles styles={ settings.styles } /> + <URLQueryController /> <FullscreenMode isActive={ isFullscreenActive } /> <UnsavedChangesWarning /> <SlotFillProvider> @@ -174,120 +189,129 @@ function Editor() { <EntityProvider kind="root" type="site"> <EntityProvider kind="postType" - type={ 'wp_template' } - id={ - templateType === 'wp_template' ? entityId : null - } + type={ templateType } + id={ entityId } > <EntityProvider kind="postType" - type="wp_template_part" + type="wp_global_styles" id={ - templateType === 'wp_template_part' - ? entityId - : null + settings.__experimentalGlobalStylesUserEntityId } > - <EntityProvider - kind="postType" - type="wp_global_styles" - id={ - settings.__experimentalGlobalStylesUserEntityId - } - > - <BlockContextProvider - value={ blockContext } + <BlockContextProvider value={ blockContext }> + <GlobalStylesProvider + baseStyles={ + settings.__experimentalGlobalStylesBaseStyles + } > - <FocusReturnProvider> - <GlobalStylesProvider - baseStyles={ - settings.__experimentalGlobalStylesBaseStyles - } - contexts={ - settings.__experimentalGlobalStylesContexts - } - > - <KeyboardShortcuts.Register /> - <SidebarComplementaryAreaFills /> - <InterfaceSkeleton - labels={ interfaceLabels } - drawer={ - <NavigationSidebar /> - } - leftSidebar={ - <LeftSidebar /> - } - sidebar={ - sidebarIsOpened && ( - <ComplementaryArea.Slot scope="core/edit-site" /> - ) - } - header={ - <Header - openEntitiesSavedStates={ - openEntitiesSavedStates - } - /> - } - content={ - <BlockSelectionClearer - className="edit-site-visual-editor" - style={ - inlineStyles - } - > - <Notices /> - <Popover.Slot name="block-toolbar" /> - { template && ( - <BlockEditor - setIsInserterOpen={ - setIsInserterOpened - } - /> - ) } - <KeyboardShortcuts /> - </BlockSelectionClearer> - } - actions={ - <> - <EntitiesSavedStates - isOpen={ - isEntitiesSavedStatesOpen - } - close={ - closeEntitiesSavedStates + <KeyboardShortcuts.Register /> + <SidebarComplementaryAreaFills /> + <InterfaceSkeleton + ref={ ref } + labels={ interfaceLabels } + drawer={ <NavigationSidebar /> } + secondarySidebar={ + isInserterOpen ? ( + <div + ref={ + inserterDialogRef + } + { ...inserterDialogProps } + className="edit-site-editor__inserter-panel" + > + <div className="edit-site-editor__inserter-panel-header"> + <Button + icon={ close } + onClick={ () => + setIsInserterOpened( + false + ) } /> - { ! isEntitiesSavedStatesOpen && ( - <div className="edit-site-editor__toggle-save-panel"> - <Button - isSecondary - className="edit-site-editor__toggle-save-panel-button" - onClick={ - openEntitiesSavedStates - } - aria-expanded={ + </div> + <div className="edit-site-editor__inserter-panel-content"> + <Library + showInserterHelpPanel + onSelect={ () => { + if ( + isMobile + ) { + setIsInserterOpened( false - } - > - { __( - 'Open save panel' - ) } - </Button> - </div> - ) } - </> - } - footer={ - <BlockBreadcrumb /> + ); + } + } } + /> + </div> + </div> + ) : null + } + sidebar={ + sidebarIsOpened && ( + <ComplementaryArea.Slot scope="core/edit-site" /> + ) + } + header={ + <Header + openEntitiesSavedStates={ + openEntitiesSavedStates } /> - <Popover.Slot /> - <PluginArea /> - </GlobalStylesProvider> - </FocusReturnProvider> - </BlockContextProvider> - </EntityProvider> + } + content={ + <div + className="edit-site-visual-editor" + style={ inlineStyles } + > + <Notices /> + <Popover.Slot name="block-toolbar" /> + { template && ( + <BlockEditor + setIsInserterOpen={ + setIsInserterOpened + } + /> + ) } + <KeyboardShortcuts /> + </div> + } + actions={ + <> + <EntitiesSavedStates + isOpen={ + isEntitiesSavedStatesOpen + } + close={ + closeEntitiesSavedStates + } + /> + { ! isEntitiesSavedStatesOpen && ( + <div className="edit-site-editor__toggle-save-panel"> + <Button + isSecondary + className="edit-site-editor__toggle-save-panel-button" + onClick={ + openEntitiesSavedStates + } + aria-expanded={ + false + } + > + { __( + 'Open save panel' + ) } + </Button> + </div> + ) } + </> + } + footer={ <BlockBreadcrumb /> } + /> + <Popover.Slot /> + <PluginArea /> + </GlobalStylesProvider> + </BlockContextProvider> </EntityProvider> </EntityProvider> </EntityProvider> diff --git a/packages/edit-site/src/components/editor/style.scss b/packages/edit-site/src/components/editor/style.scss index ca94a01191267c..cb8048a51cd5c4 100644 --- a/packages/edit-site/src/components/editor/style.scss +++ b/packages/edit-site/src/components/editor/style.scss @@ -21,5 +21,32 @@ } .edit-site-visual-editor { + position: relative; background-color: $white; } + +.edit-site-editor__inserter-panel { + height: 100%; + display: flex; + flex-direction: column; +} + +.edit-site-editor__inserter-panel-header { + padding-top: $grid-unit-10; + padding-right: $grid-unit-10; + display: flex; + justify-content: flex-end; + + @include break-medium() { + display: none; + } +} + +.edit-site-editor__inserter-panel-content { + // Leave space for the close button + height: calc(100% - #{$button-size} - #{$grid-unit-10}); + + @include break-medium() { + height: 100%; + } +} diff --git a/packages/edit-site/src/components/editor/utils.js b/packages/edit-site/src/components/editor/utils.js index 2655b5f86af480..1bd54dc32d675b 100644 --- a/packages/edit-site/src/components/editor/utils.js +++ b/packages/edit-site/src/components/editor/utils.js @@ -1,22 +1,199 @@ +/** + * External dependencies + */ +import { get, find, forEach, camelCase, isString } from 'lodash'; +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; + /* Supporting data */ -export const GLOBAL_CONTEXT = 'global'; -export const PRESET_CATEGORIES = { - color: { path: [ 'color', 'palette' ], key: 'color' }, - gradient: { path: [ 'color', 'gradients' ], key: 'gradient' }, - fontSize: { path: [ 'typography', 'fontSizes' ], key: 'size' }, +export const GLOBAL_CONTEXT_NAME = 'global'; +export const GLOBAL_CONTEXT_SELECTOR = ':root'; +export const GLOBAL_CONTEXT_SUPPORTS = [ + '--wp--style--color--link', + 'background', + 'backgroundColor', + 'color', + 'fontFamily', + 'fontSize', + 'fontStyle', + 'fontWeight', + 'lineHeight', + 'textDecoration', + 'textTransform', +]; + +export const PRESET_METADATA = [ + { + path: [ 'settings', 'color', 'palette' ], + valueKey: 'color', + cssVarInfix: 'color', + classes: [ + { classSuffix: 'color', propertyName: 'color' }, + { + classSuffix: 'background-color', + propertyName: 'background-color', + }, + ], + }, + { + path: [ 'settings', 'color', 'gradients' ], + valueKey: 'gradient', + cssVarInfix: 'gradient', + classes: [ + { + classSuffix: 'gradient-background', + propertyName: 'background', + }, + ], + }, + { + path: [ 'settings', 'typography', 'fontSizes' ], + valueKey: 'size', + cssVarInfix: 'font-size', + classes: [ { classSuffix: 'font-size', propertyName: 'font-size' } ], + }, + { + path: [ 'settings', 'typography', 'fontFamilies' ], + valueKey: 'fontFamily', + cssVarInfix: 'font-family', + classes: [], + }, +]; + +const STYLE_PROPERTIES_TO_CSS_VAR_INFIX = { + backgroundColor: 'color', + LINK_COLOR: 'color', + background: 'gradient', }; + +function getPresetMetadataFromStyleProperty( styleProperty ) { + if ( ! getPresetMetadataFromStyleProperty.MAP ) { + getPresetMetadataFromStyleProperty.MAP = {}; + PRESET_METADATA.forEach( ( { cssVarInfix }, index ) => { + getPresetMetadataFromStyleProperty.MAP[ camelCase( cssVarInfix ) ] = + PRESET_METADATA[ index ]; + } ); + forEach( STYLE_PROPERTIES_TO_CSS_VAR_INFIX, ( value, key ) => { + getPresetMetadataFromStyleProperty.MAP[ key ] = + getPresetMetadataFromStyleProperty.MAP[ value ]; + } ); + } + return getPresetMetadataFromStyleProperty.MAP[ styleProperty ]; +} + export const LINK_COLOR = '--wp--style--color--link'; export const LINK_COLOR_DECLARATION = `a { color: var(${ LINK_COLOR }, #00e); }`; -/* Helpers for unit processing */ -export const fromPx = ( value ) => { - switch ( typeof value ) { - case 'string': - return +value.replace( 'px', '' ); - case 'number': - default: - return value; +export function useEditorFeature( + featurePath, + blockName = GLOBAL_CONTEXT_NAME +) { + const settings = useSelect( ( select ) => { + return select( 'core/edit-site' ).getSettings(); + } ); + return ( + get( + settings, + `__experimentalFeatures.${ blockName }.${ featurePath }` + ) ?? + get( + settings, + `__experimentalFeatures.${ GLOBAL_CONTEXT_NAME }.${ featurePath }` + ) + ); +} + +export function getPresetVariable( styles, blockName, propertyName, value ) { + if ( ! value ) { + return value; } -}; + const presetData = getPresetMetadataFromStyleProperty( propertyName ); + if ( ! presetData ) { + return value; + } + const { valueKey, path, cssVarInfix } = presetData; + const presets = + get( styles, [ blockName, ...path ] ) ?? + get( styles, [ GLOBAL_CONTEXT_NAME, ...path ] ); + const presetObject = find( presets, ( preset ) => { + return preset[ valueKey ] === value; + } ); + if ( ! presetObject ) { + return value; + } + return `var:preset|${ cssVarInfix }|${ presetObject.slug }`; +} -export const toPx = ( value ) => ( value ? value + 'px' : value ); +function getValueFromPresetVariable( + styles, + blockName, + variable, + [ presetType, slug ] +) { + presetType = camelCase( presetType ); + const presetData = getPresetMetadataFromStyleProperty( presetType ); + if ( ! presetData ) { + return variable; + } + const presets = + get( styles, [ blockName, ...presetData.path ] ) ?? + get( styles, [ GLOBAL_CONTEXT_NAME, ...presetData.path ] ); + if ( ! presets ) { + return variable; + } + const presetObject = find( presets, ( preset ) => { + return preset.slug === slug; + } ); + if ( presetObject ) { + const { valueKey } = presetData; + const result = presetObject[ valueKey ]; + return getValueFromVariable( styles, blockName, result ); + } + return variable; +} + +function getValueFromCustomVariable( styles, blockName, variable, path ) { + const result = + get( styles, [ blockName, 'settings', 'custom', ...path ] ) ?? + get( styles, [ GLOBAL_CONTEXT_NAME, 'settings', 'custom', ...path ] ); + if ( ! result ) { + return variable; + } + // A variable may reference another variable so we need recursion until we find the value. + return getValueFromVariable( styles, blockName, result ); +} + +export function getValueFromVariable( styles, blockName, variable ) { + if ( ! variable || ! isString( variable ) ) { + return variable; + } + let parsedVar; + const INTERNAL_REFERENCE_PREFIX = 'var:'; + const CSS_REFERENCE_PREFIX = 'var(--wp--'; + const CSS_REFERENCE_SUFFIX = ')'; + if ( variable.startsWith( INTERNAL_REFERENCE_PREFIX ) ) { + parsedVar = variable + .slice( INTERNAL_REFERENCE_PREFIX.length ) + .split( '|' ); + } else if ( + variable.startsWith( CSS_REFERENCE_PREFIX ) && + variable.endsWith( CSS_REFERENCE_SUFFIX ) + ) { + parsedVar = variable + .slice( CSS_REFERENCE_PREFIX.length, -CSS_REFERENCE_SUFFIX.length ) + .split( '--' ); + } else { + return variable; + } + + const [ type, ...path ] = parsedVar; + if ( type === 'preset' ) { + return getValueFromPresetVariable( styles, blockName, variable, path ); + } + if ( type === 'custom' ) { + return getValueFromCustomVariable( styles, blockName, variable, path ); + } + return variable; +} diff --git a/packages/edit-site/src/components/header/document-actions/style.scss b/packages/edit-site/src/components/header/document-actions/style.scss index 8016763f3e4d3a..97ff4d1ea5b661 100644 --- a/packages/edit-site/src/components/header/document-actions/style.scss +++ b/packages/edit-site/src/components/header/document-actions/style.scss @@ -67,4 +67,5 @@ .edit-site-document-actions__info-dropdown > .components-popover__content > div { padding: 0; + min-width: 240px; } diff --git a/packages/edit-site/src/components/header/fullscreen-mode-close/index.js b/packages/edit-site/src/components/header/fullscreen-mode-close/index.js deleted file mode 100644 index 83a07089580491..00000000000000 --- a/packages/edit-site/src/components/header/fullscreen-mode-close/index.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * WordPress dependencies - */ -import { useSelect } from '@wordpress/data'; -import { Button, Icon } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { wordpress } from '@wordpress/icons'; - -function FullscreenModeClose( { icon } ) { - const { isActive, isRequestingSiteIcon, siteIconUrl } = useSelect( - ( select ) => { - const { isFeatureActive } = select( 'core/edit-site' ); - const { getEntityRecord } = select( 'core' ); - const { isResolving } = select( 'core/data' ); - const siteData = - getEntityRecord( 'root', '__unstableBase', undefined ) || {}; - - return { - isActive: isFeatureActive( 'fullscreenMode' ), - isRequestingSiteIcon: isResolving( 'core', 'getEntityRecord', [ - 'root', - '__unstableBase', - undefined, - ] ), - siteIconUrl: siteData.site_icon_url, - }; - }, - [] - ); - - if ( ! isActive ) { - return null; - } - - let buttonIcon = <Icon size="36px" icon={ wordpress } />; - - if ( siteIconUrl ) { - buttonIcon = ( - <img - alt={ __( 'Site Icon' ) } - className="edit-site-fullscreen-mode-close_site-icon" - src={ siteIconUrl } - /> - ); - } else if ( isRequestingSiteIcon ) { - buttonIcon = null; - } else if ( icon ) { - buttonIcon = <Icon size="36px" icon={ icon } />; - } - - return ( - <Button - className="edit-site-fullscreen-mode-close has-icon" - href="index.php" - label={ __( 'Back' ) } - showTooltip - > - { buttonIcon } - </Button> - ); -} - -export default FullscreenModeClose; diff --git a/packages/edit-site/src/components/header/fullscreen-mode-close/style.scss b/packages/edit-site/src/components/header/fullscreen-mode-close/style.scss deleted file mode 100644 index 01e648374d8ab1..00000000000000 --- a/packages/edit-site/src/components/header/fullscreen-mode-close/style.scss +++ /dev/null @@ -1,33 +0,0 @@ -.edit-site-fullscreen-mode-close.has-icon { - // Do not show the toolbar icon on small screens, - // when Fullscreen Mode is not an option in the "More" menu. - display: none; - - @include break-medium() { - display: flex; - align-items: center; - align-self: stretch; - border: none; - background: #23282e; // WP-admin gray. - color: $white; - border-radius: 0; - height: $header-height; - min-width: $header-height; - - &.has-icon { - &:hover { - background: #32373d; // WP-admin light-gray. - } - &:active { - color: $white; - } - &:focus { - box-shadow: inset 0 0 0 $border-width-focus var(--wp-admin-theme-color), inset 0 0 0 3px $white; - } - } - } -} - -.edit-site-fullscreen-mode-close_site-icon { - width: 36px; -} diff --git a/packages/edit-site/src/components/header/fullscreen-mode-close/test/index.js b/packages/edit-site/src/components/header/fullscreen-mode-close/test/index.js deleted file mode 100644 index ee9eddcaf163e0..00000000000000 --- a/packages/edit-site/src/components/header/fullscreen-mode-close/test/index.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * External dependencies - */ -import { render } from '@testing-library/react'; - -/** - * WordPress dependencies - */ -import { useSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import FullscreenModeClose from '../'; - -jest.mock( '@wordpress/data/src/components/use-select', () => { - // This allows us to tweak the returned value on each test - const mock = jest.fn(); - return mock; -} ); - -jest.mock( '@wordpress/core-data' ); - -describe( 'FullscreenModeClose', () => { - describe( 'when in full screen mode', () => { - it( 'should display a user uploaded site icon if it exists', () => { - useSelect.mockImplementation( ( cb ) => { - return cb( () => ( { - isResolving: () => false, - isFeatureActive: () => true, - getEntityRecord: () => ( { - site_icon_url: 'https://fakeUrl.com', - } ), - } ) ); - } ); - - const { container } = render( <FullscreenModeClose /> ); - const siteIcon = container.querySelector( - '.edit-site-fullscreen-mode-close_site-icon' - ); - - expect( siteIcon ).toBeTruthy(); - } ); - - it( 'should display a default site icon if no user uploaded site icon exists', () => { - useSelect.mockImplementation( ( cb ) => { - return cb( () => ( { - isResolving: () => false, - isFeatureActive: () => true, - getEntityRecord: () => ( { - site_icon_url: '', - } ), - } ) ); - } ); - - const { container } = render( <FullscreenModeClose /> ); - const siteIcon = container.querySelector( - '.edit-site-fullscreen-mode-close_site-icon' - ); - const defaultIcon = container.querySelector( 'svg' ); - - expect( siteIcon ).toBeFalsy(); - expect( defaultIcon ).toBeTruthy(); - } ); - } ); -} ); diff --git a/packages/edit-site/src/components/header/index.js b/packages/edit-site/src/components/header/index.js index 35a218feecad90..2567a3cfea10f7 100644 --- a/packages/edit-site/src/components/header/index.js +++ b/packages/edit-site/src/components/header/index.js @@ -23,14 +23,13 @@ import UndoButton from './undo-redo/undo'; import RedoButton from './undo-redo/redo'; import DocumentActions from './document-actions'; import TemplateDetails from '../template-details'; -import { getTemplateInfo } from '../../utils'; export default function Header( { openEntitiesSavedStates } ) { const { deviceType, + entityTitle, hasFixedToolbar, template, - templatePart, templateType, isInserterOpen, } = useSelect( ( select ) => { @@ -43,20 +42,25 @@ export default function Header( { openEntitiesSavedStates } ) { isInserterOpened, } = select( 'core/edit-site' ); const { getEntityRecord } = select( 'core' ); + const { __experimentalGetTemplateInfo: getTemplateInfo } = select( + 'core/editor' + ); - const templatePartId = getTemplatePartId(); - const templateId = getTemplateId(); + const postType = getTemplateType(); + const postId = + postType === 'wp_template' ? getTemplateId() : getTemplatePartId(); + const record = getEntityRecord( 'postType', postType, postId ); + const _entityTitle = + 'wp_template' === postType + ? getTemplateInfo( record ).title + : record?.slug; return { deviceType: __experimentalGetPreviewDeviceType(), + entityTitle: _entityTitle, hasFixedToolbar: isFeatureActive( 'fixedToolbar' ), - template: getEntityRecord( 'postType', 'wp_template', templateId ), - templatePart: getEntityRecord( - 'postType', - 'wp_template_part', - templatePartId - ), - templateType: getTemplateType(), + template: record, + templateType: postType, isInserterOpen: isInserterOpened(), }; }, [] ); @@ -70,11 +74,6 @@ export default function Header( { openEntitiesSavedStates } ) { const displayBlockToolbar = ! isLargeViewport || deviceType !== 'Desktop' || hasFixedToolbar; - let { title } = getTemplateInfo( template ); - if ( 'wp_template_part' === templateType ) { - title = templatePart?.slug; - } - return ( <div className="edit-site-header"> <div className="edit-site-header_start"> @@ -92,10 +91,14 @@ export default function Header( { openEntitiesSavedStates } ) { 'Generic label for block inserter button' ) } /> - <ToolSelector /> - <UndoButton /> - <RedoButton /> - <BlockNavigationDropdown /> + { isLargeViewport && ( + <> + <ToolSelector /> + <UndoButton /> + <RedoButton /> + <BlockNavigationDropdown /> + </> + ) } { displayBlockToolbar && ( <div className="edit-site-header-toolbar__block-toolbar"> <BlockToolbar hideDragHandle /> @@ -105,22 +108,25 @@ export default function Header( { openEntitiesSavedStates } ) { </div> <div className="edit-site-header_center"> - <DocumentActions - entityTitle={ title } - entityLabel={ - templateType === 'wp_template' - ? 'template' - : 'template part' - } - > - { templateType === 'wp_template' && - ( ( { onClose } ) => ( + { 'wp_template' === templateType && ( + <DocumentActions + entityTitle={ entityTitle } + entityLabel="template" + > + { ( { onClose } ) => ( <TemplateDetails template={ template } onClose={ onClose } /> - ) ) } - </DocumentActions> + ) } + </DocumentActions> + ) } + { 'wp_template_part' === templateType && ( + <DocumentActions + entityTitle={ entityTitle } + entityLabel="template part" + /> + ) } </div> <div className="edit-site-header_end"> diff --git a/packages/edit-site/src/components/header/style.scss b/packages/edit-site/src/components/header/style.scss index 320a597c39d4fd..130f2a069b744a 100644 --- a/packages/edit-site/src/components/header/style.scss +++ b/packages/edit-site/src/components/header/style.scss @@ -1,3 +1,5 @@ +$header-toolbar-min-width: 335px; + .edit-site-header { align-items: center; background-color: $white; @@ -5,25 +7,27 @@ height: $header-height; box-sizing: border-box; width: 100%; - padding-left: $header-height; + justify-content: space-between; + + @include break-medium() { + body.is-fullscreen-mode & { + padding-left: 60px; + transition: padding-left 20ms linear; + transition-delay: 80ms; + @include reduce-motion("transition"); + } + + } .edit-site-header_start, .edit-site-header_end { - flex: 1 0; display: flex; } - .edit-site-header_start { - // Flex basis prevents the header_start toolbar - // from collapsing when shrinking the viewport. - flex-basis: calc(#{$header-toolbar-min-width} - #{$header-height}); - } - .edit-site-header_center { display: flex; align-items: center; height: 100%; - flex-shrink: 1; // Flex items will, by default, refuse to shrink below a minimum // intrinsic width. In order to shrink this flexbox item, and // subsequently truncate child text, we set an explicit min-width. @@ -32,9 +36,6 @@ } .edit-site-header_end { - // Flex basis prevents the header_end toolbar - // from collapsing when shrinking the viewport - flex-basis: $header-toolbar-min-width; justify-content: flex-end; } } @@ -43,17 +44,36 @@ body.is-navigation-sidebar-open { .edit-site-header { padding-left: 0; + transition: padding-left 20ms linear; + transition-delay: 0ms; + } +} - .edit-site-header_start { - flex-basis: $header-toolbar-min-width; +// Centred document title on small screens with sidebar open +@media ( max-width: #{ ($break-large - 1) } ) { + body.is-navigation-sidebar-open .edit-site-header { + .edit-site-header-toolbar__inserter-toggle ~ .components-button, + .edit-site-header_end .components-button:not(.is-primary) { + display: none; + } + .edit-site-save-button__button { + margin-right: 0; } } } .edit-site-header__toolbar { display: flex; - padding-left: $grid-unit-30; align-items: center; + padding-left: $grid-unit-10; + + @include break-small() { + padding-left: $grid-unit-30; + } + + @include break-wide() { + padding-right: $grid-unit-10; + } .edit-site-header-toolbar__inserter-toggle { margin-right: $grid-unit-10; @@ -83,6 +103,14 @@ body.is-navigation-sidebar-open { flex-wrap: wrap; padding-right: $grid-unit-05; + .interface-pinned-items { + display: none; + + @include break-medium() { + display: block; + } + } + // Adjust button paddings to scale better to mobile. .editor-post-saved-state, .components-button.components-button { @@ -95,7 +123,7 @@ body.is-navigation-sidebar-open { .editor-post-saved-state, .components-button.is-tertiary { - padding: 0 #{ $grid-unit-15 / 2 }; + padding: 0 #{$grid-unit-15 / 2}; } .edit-site-more-menu .components-button, @@ -103,7 +131,7 @@ body.is-navigation-sidebar-open { margin-right: 0; } - @include break-small () { + @include break-small() { padding-right: $grid-unit-20; } } diff --git a/packages/edit-site/src/components/keyboard-shortcuts/index.js b/packages/edit-site/src/components/keyboard-shortcuts/index.js index a0588857ea2451..75fe5b085c31d6 100644 --- a/packages/edit-site/src/components/keyboard-shortcuts/index.js +++ b/packages/edit-site/src/components/keyboard-shortcuts/index.js @@ -2,7 +2,10 @@ * WordPress dependencies */ import { useEffect } from '@wordpress/element'; -import { useShortcut } from '@wordpress/keyboard-shortcuts'; +import { + useShortcut, + store as keyboardShortcutsStore, +} from '@wordpress/keyboard-shortcuts'; import { useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; @@ -31,7 +34,7 @@ function KeyboardShortcuts() { } function KeyboardShortcutsRegister() { // Registering the shortcuts - const { registerShortcut } = useDispatch( 'core/keyboard-shortcuts' ); + const { registerShortcut } = useDispatch( keyboardShortcutsStore ); useEffect( () => { registerShortcut( { name: 'core/edit-site/undo', diff --git a/packages/edit-site/src/components/left-sidebar/index.js b/packages/edit-site/src/components/left-sidebar/index.js deleted file mode 100644 index bb5ed6a0cb5332..00000000000000 --- a/packages/edit-site/src/components/left-sidebar/index.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * WordPress dependencies - */ -import { useSelect, useDispatch } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import InserterPanel from './inserter-panel'; - -const LeftSidebar = () => { - const { isInserterOpen } = useSelect( ( select ) => { - const { isInserterOpened } = select( 'core/edit-site' ); - return { - isInserterOpen: isInserterOpened(), - }; - } ); - - const { setIsInserterOpened } = useDispatch( 'core/edit-site' ); - - if ( isInserterOpen ) { - return ( - <InserterPanel - closeInserter={ () => setIsInserterOpened( false ) } - /> - ); - } - - return null; -}; - -export default LeftSidebar; diff --git a/packages/edit-site/src/components/left-sidebar/inserter-panel/index.js b/packages/edit-site/src/components/left-sidebar/inserter-panel/index.js deleted file mode 100644 index 5fb427f19f0a7f..00000000000000 --- a/packages/edit-site/src/components/left-sidebar/inserter-panel/index.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * WordPress dependencies - */ -import { __experimentalLibrary as Library } from '@wordpress/block-editor'; -import { Button } from '@wordpress/components'; -import { useViewportMatch } from '@wordpress/compose'; -import { close } from '@wordpress/icons'; - -const InserterPanel = ( { closeInserter } ) => { - const isMobile = useViewportMatch( 'medium', '<' ); - - return ( - <div className="edit-site-inserter-panel"> - <div className="edit-site-inserter-panel__header"> - <Button icon={ close } onClick={ closeInserter } /> - </div> - <div className="edit-site-inserter-panel__content"> - <Library - showInserterHelpPanel - onSelect={ () => { - if ( isMobile ) { - closeInserter(); - } - } } - /> - </div> - </div> - ); -}; - -export default InserterPanel; diff --git a/packages/edit-site/src/components/left-sidebar/inserter-panel/style.scss b/packages/edit-site/src/components/left-sidebar/inserter-panel/style.scss deleted file mode 100644 index 6a3f030c4bd3d3..00000000000000 --- a/packages/edit-site/src/components/left-sidebar/inserter-panel/style.scss +++ /dev/null @@ -1,25 +0,0 @@ -.edit-site-inserter-panel { - height: 100%; - display: flex; - flex-direction: column; -} - -.edit-site-inserter-panel__header { - padding-top: $grid-unit-10; - padding-right: $grid-unit-10; - display: flex; - justify-content: flex-end; - - @include break-medium() { - display: none; - } -} - -.edit-site-inserter-panel__content { - // Leave space for the close button - height: calc(100% - #{$button-size} - #{$grid-unit-10}); - - @include break-medium() { - height: 100%; - } -} diff --git a/packages/edit-site/src/components/main-dashboard-button/index.js b/packages/edit-site/src/components/main-dashboard-button/index.js new file mode 100644 index 00000000000000..efc3bfaad19e87 --- /dev/null +++ b/packages/edit-site/src/components/main-dashboard-button/index.js @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +import { + __experimentalUseSlot as useSlot, + createSlotFill, +} from '@wordpress/components'; + +const slotName = '__experimentalMainDashboardButton'; + +const { Fill, Slot: MainDashboardButtonSlot } = createSlotFill( slotName ); + +const MainDashboardButton = Fill; + +const Slot = ( { children } ) => { + const slot = useSlot( slotName ); + const hasFills = Boolean( slot.fills && slot.fills.length ); + + if ( ! hasFills ) { + return children; + } + + return <MainDashboardButtonSlot bubblesVirtually />; +}; + +MainDashboardButton.Slot = Slot; + +export default MainDashboardButton; diff --git a/packages/edit-site/src/components/navigation-sidebar/index.js b/packages/edit-site/src/components/navigation-sidebar/index.js index ea5b1e21a0e2c2..28432f5e056772 100644 --- a/packages/edit-site/src/components/navigation-sidebar/index.js +++ b/packages/edit-site/src/components/navigation-sidebar/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { createSlotFill } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; /** @@ -9,7 +10,12 @@ import { useSelect } from '@wordpress/data'; import NavigationPanel from './navigation-panel'; import NavigationToggle from './navigation-toggle'; -const NavigationSidebar = () => { +export const { + Fill: NavigationPanelPreviewFill, + Slot: NavigationPanelPreviewSlot, +} = createSlotFill( 'EditSiteNavigationPanelPreview' ); + +export default function NavigationSidebar() { const isNavigationOpen = useSelect( ( select ) => { return select( 'core/edit-site' ).isNavigationOpened(); } ); @@ -17,9 +23,8 @@ const NavigationSidebar = () => { return ( <> <NavigationToggle isOpen={ isNavigationOpen } /> - { isNavigationOpen && <NavigationPanel /> } + <NavigationPanel isOpen={ isNavigationOpen } /> + <NavigationPanelPreviewSlot /> </> ); -}; - -export default NavigationSidebar; +} diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/constants.js b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/constants.js index 3d6425735db8b8..e0201af3420970 100644 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/constants.js +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/constants.js @@ -9,6 +9,18 @@ export const TEMPLATES_GENERAL = [ export const TEMPLATES_POSTS = [ 'home', 'single' ]; +export const TEMPLATES_STATUSES = [ 'publish', 'draft', 'auto-draft' ]; + +export const TEMPLATES_NEW_OPTIONS = [ + 'front-page', + 'single-post', + 'page', + 'archive', + 'search', + '404', + 'index', +]; + export const MENU_ROOT = 'root'; export const MENU_CONTENT_CATEGORIES = 'content-categories'; export const MENU_CONTENT_PAGES = 'content-pages'; diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/index.js b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/index.js index 7f79d0f1105fde..9db6e54cc87fe5 100644 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/index.js +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/index.js @@ -1,8 +1,12 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ -import { useState } from '@wordpress/element'; -import { createSlotFill } from '@wordpress/components'; +import { useState, useEffect, useRef } from '@wordpress/element'; /** * Internal dependencies @@ -12,29 +16,58 @@ import TemplatesNavigation from './templates-navigation'; import { useSelect } from '@wordpress/data'; import { MENU_ROOT } from './constants'; -export const { - Fill: NavigationPanelPreviewFill, - Slot: NavigationPanelPreviewSlot, -} = createSlotFill( 'EditSiteNavigationPanelPreview' ); - -const NavigationPanel = () => { +const NavigationPanel = ( { isOpen } ) => { const [ contentActiveMenu, setContentActiveMenu ] = useState( MENU_ROOT ); - const templatesActiveMenu = useSelect( - ( select ) => select( 'core/edit-site' ).getNavigationPanelActiveMenu(), - [] - ); + const { templatesActiveMenu, siteTitle } = useSelect( ( select ) => { + const { getNavigationPanelActiveMenu } = select( 'core/edit-site' ); + const { getEntityRecord } = select( 'core' ); - return ( - <div className="edit-site-navigation-panel"> - { ( contentActiveMenu === MENU_ROOT || - templatesActiveMenu !== MENU_ROOT ) && <TemplatesNavigation /> } + const siteData = + getEntityRecord( 'root', '__unstableBase', undefined ) || {}; + + return { + templatesActiveMenu: getNavigationPanelActiveMenu(), + siteTitle: siteData.name, + }; + }, [] ); - { ( templatesActiveMenu === MENU_ROOT || - contentActiveMenu !== MENU_ROOT ) && ( - <ContentNavigation onActivateMenu={ setContentActiveMenu } /> - ) } + // Ensures focus is moved to the panel area when it is activated + // from a separate component (such as document actions in the header). + const panelRef = useRef(); + useEffect( () => { + if ( isOpen ) { + panelRef.current.focus(); + } + }, [ templatesActiveMenu ] ); + + return ( + <div + className={ classnames( `edit-site-navigation-panel`, { + 'is-open': isOpen, + } ) } + ref={ panelRef } + tabIndex="-1" + > + <div className="edit-site-navigation-panel__inner"> + <div className="edit-site-navigation-panel__site-title-container"> + <div className="edit-site-navigation-panel__site-title"> + { siteTitle } + </div> + </div> - <NavigationPanelPreviewSlot /> + <div className="edit-site-navigation-panel__scroll-container"> + { ( contentActiveMenu === MENU_ROOT || + templatesActiveMenu !== MENU_ROOT ) && ( + <TemplatesNavigation /> + ) } + { ( templatesActiveMenu === MENU_ROOT || + contentActiveMenu !== MENU_ROOT ) && ( + <ContentNavigation + onActivateMenu={ setContentActiveMenu } + /> + ) } + </div> + </div> </div> ); }; diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/template-parts.js b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/template-parts.js index 27336f6c4aeb9f..c2d8a919714d9a 100644 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/template-parts.js +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/template-parts.js @@ -1,28 +1,42 @@ +/** + * External dependencies + */ +import { map } from 'lodash'; + /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; import { - __experimentalNavigationItem as NavigationItem, __experimentalNavigationMenu as NavigationMenu, + __experimentalNavigationItem as NavigationItem, } from '@wordpress/components'; +import { useState, useCallback } from '@wordpress/element'; /** * Internal dependencies */ -import TemplateNavigationItems from '../template-navigation-items'; +import TemplateNavigationItem from '../template-navigation-item'; import { MENU_ROOT, MENU_TEMPLATE_PARTS } from '../constants'; +import SearchResults from '../search-results'; + +export default function TemplatePartsMenu() { + const [ search, setSearch ] = useState( '' ); + const onSearch = useCallback( ( value ) => { + setSearch( value ); + } ); -export default function TemplatePartsMenu( { onActivateItem } ) { const templateParts = useSelect( ( select ) => { - return select( 'core' ).getEntityRecords( - 'postType', - 'wp_template_part', - { + const unfilteredTemplateParts = + select( 'core' ).getEntityRecords( 'postType', 'wp_template_part', { status: [ 'publish', 'auto-draft' ], per_page: -1, - } + } ) || []; + const currentTheme = select( 'core' ).getCurrentTheme()?.stylesheet; + return unfilteredTemplateParts.filter( + ( item ) => + item.status === 'publish' || item.wp_theme_slug === currentTheme ); }, [] ); @@ -31,14 +45,25 @@ export default function TemplatePartsMenu( { onActivateItem } ) { menu={ MENU_TEMPLATE_PARTS } title={ __( 'Template Parts' ) } parentMenu={ MENU_ROOT } + hasSearch={ true } + onSearch={ onSearch } + search={ search } > - <TemplateNavigationItems - entityType="wp_template_part" - templates={ templateParts } - onActivateItem={ onActivateItem } - /> + { search && ( + <SearchResults items={ templateParts } search={ search } /> + ) } + + { ! search && + map( templateParts, ( templatePart ) => ( + <TemplateNavigationItem + item={ templatePart } + key={ `wp_template_part-${ templatePart.id }` } + /> + ) ) } - { ! templateParts && <NavigationItem title={ __( 'Loading…' ) } /> } + { ! search && templateParts === null && ( + <NavigationItem title={ __( 'Loading…' ) } isText /> + ) } </NavigationMenu> ); } diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates-all.js b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates-all.js index ce0685d1931c65..934a35bd0e0f5c 100644 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates-all.js +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates-all.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { map } from 'lodash'; + /** * WordPress dependencies */ @@ -7,20 +12,22 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import TemplateNavigationItems from '../template-navigation-items'; +import TemplateNavigationItem from '../template-navigation-item'; import { MENU_TEMPLATES, MENU_TEMPLATES_ALL } from '../constants'; -export default function TemplatesAllMenu( { templates, onActivateItem } ) { +export default function TemplatesAllMenu( { templates } ) { return ( <NavigationMenu menu={ MENU_TEMPLATES_ALL } title={ __( 'All Templates' ) } parentMenu={ MENU_TEMPLATES } > - <TemplateNavigationItems - templates={ templates } - onActivateItem={ onActivateItem } - /> + { map( templates, ( template ) => ( + <TemplateNavigationItem + item={ template } + key={ `wp_template-${ template.id }` } + /> + ) ) } </NavigationMenu> ); } diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates-pages.js b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates-pages.js index f535da47ae5952..d4816219a02399 100644 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates-pages.js +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates-pages.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { map } from 'lodash'; + /** * WordPress dependencies */ @@ -10,34 +15,33 @@ import { __, _x } from '@wordpress/i18n'; /** * Internal dependencies */ -import TemplateNavigationItems from '../template-navigation-items'; +import TemplateNavigationItem from '../template-navigation-item'; import { MENU_TEMPLATES, MENU_TEMPLATES_PAGES } from '../constants'; -export default function TemplatesPagesMenu( { templates, onActivateItem } ) { +export default function TemplatesPagesMenu( { templates } ) { const defaultTemplate = templates?.find( ( { slug } ) => slug === 'page' ); - const specificPageTemplates = templates?.filter( ( { slug } ) => - slug.startsWith( 'page-' ) - ); + const specificTemplates = + templates?.filter( ( { slug } ) => slug.startsWith( 'page-' ) ) ?? []; return ( <NavigationMenu menu={ MENU_TEMPLATES_PAGES } title={ __( 'Pages' ) } parentMenu={ MENU_TEMPLATES } + isEmpty={ ! defaultTemplate && specificTemplates.length === 0 } > <NavigationGroup title={ _x( 'Specific', 'specific templates' ) }> - <TemplateNavigationItems - templates={ specificPageTemplates } - onActivateItem={ onActivateItem } - /> + { map( specificTemplates, ( template ) => ( + <TemplateNavigationItem + item={ template } + key={ `wp_template-${ template.id }` } + /> + ) ) } </NavigationGroup> { defaultTemplate && ( <NavigationGroup title={ _x( 'General', 'general templates' ) }> - <TemplateNavigationItems - templates={ defaultTemplate } - onActivateItem={ onActivateItem } - /> + <TemplateNavigationItem item={ defaultTemplate } /> </NavigationGroup> ) } </NavigationMenu> diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates-posts.js b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates-posts.js index d4c74b3109d207..02225ee5bfb247 100644 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates-posts.js +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates-posts.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { map } from 'lodash'; + /** * WordPress dependencies */ @@ -10,39 +15,45 @@ import { __, _x } from '@wordpress/i18n'; /** * Internal dependencies */ -import TemplateNavigationItems from '../template-navigation-items'; +import TemplateNavigationItem from '../template-navigation-item'; import { MENU_TEMPLATES, MENU_TEMPLATES_POSTS, TEMPLATES_POSTS, } from '../constants'; -export default function TemplatePostsMenu( { templates, onActivateItem } ) { - const generalTemplates = templates?.find( ( { slug } ) => - TEMPLATES_POSTS.includes( slug ) - ); - const specificTemplates = templates?.filter( ( { slug } ) => - slug.startsWith( 'post-' ) - ); +export default function TemplatesPostsMenu( { templates } ) { + const generalTemplates = + templates?.filter( ( { slug } ) => TEMPLATES_POSTS.includes( slug ) ) ?? + []; + const specificTemplates = + templates?.filter( ( { slug } ) => slug.startsWith( 'post-' ) ) ?? []; return ( <NavigationMenu menu={ MENU_TEMPLATES_POSTS } title={ __( 'Posts' ) } parentMenu={ MENU_TEMPLATES } + isEmpty={ + generalTemplates.length === 0 && specificTemplates.length === 0 + } > <NavigationGroup title={ _x( 'Specific', 'specific templates' ) }> - <TemplateNavigationItems - templates={ specificTemplates } - onActivateItem={ onActivateItem } - /> + { map( specificTemplates, ( template ) => ( + <TemplateNavigationItem + item={ template } + key={ `wp_template-${ template.id }` } + /> + ) ) } </NavigationGroup> <NavigationGroup title={ _x( 'General', 'general templates' ) }> - <TemplateNavigationItems - templates={ generalTemplates } - onActivateItem={ onActivateItem } - /> + { map( generalTemplates, ( template ) => ( + <TemplateNavigationItem + item={ template } + key={ `wp_template-${ template.id }` } + /> + ) ) } </NavigationGroup> </NavigationMenu> ); diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates.js b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates.js index 8a242ea83323d8..c3f15d44d10ed2 100644 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates.js +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/menus/templates.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { map } from 'lodash'; + /** * WordPress dependencies */ @@ -5,14 +10,15 @@ import { __experimentalNavigationItem as NavigationItem, __experimentalNavigationMenu as NavigationMenu, } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; import { __, _x } from '@wordpress/i18n'; +import { useState, useCallback } from '@wordpress/element'; /** * Internal dependencies */ import TemplatesPagesMenu from './templates-pages'; -import TemplateNavigationItems from '../template-navigation-items'; -import TemplatePostsMenu from './templates-posts'; +import TemplatesPostsMenu from './templates-posts'; import { MENU_ROOT, MENU_TEMPLATES, @@ -20,15 +26,23 @@ import { MENU_TEMPLATES_PAGES, MENU_TEMPLATES_POSTS, TEMPLATES_GENERAL, + TEMPLATES_STATUSES, } from '../constants'; -import { useSelect } from '@wordpress/data'; import TemplatesAllMenu from './templates-all'; +import NewTemplateDropdown from '../new-template-dropdown'; +import TemplateNavigationItem from '../template-navigation-item'; +import SearchResults from '../search-results'; + +export default function TemplatesMenu() { + const [ search, setSearch ] = useState( '' ); + const onSearch = useCallback( ( value ) => { + setSearch( value ); + } ); -export default function TemplatesMenu( { onActivateItem } ) { const templates = useSelect( ( select ) => select( 'core' ).getEntityRecords( 'postType', 'wp_template', { - status: [ 'publish', 'auto-draft' ], + status: TEMPLATES_STATUSES, per_page: -1, } ), [] @@ -42,40 +56,48 @@ export default function TemplatesMenu( { onActivateItem } ) { <NavigationMenu menu={ MENU_TEMPLATES } title={ __( 'Templates' ) } + titleAction={ <NewTemplateDropdown /> } parentMenu={ MENU_ROOT } + hasSearch={ true } + onSearch={ onSearch } + search={ search } > - <NavigationItem - navigateToMenu={ MENU_TEMPLATES_ALL } - title={ _x( 'All', 'all templates' ) } - /> - <NavigationItem - navigateToMenu={ MENU_TEMPLATES_PAGES } - title={ __( 'Pages' ) } - /> - <NavigationItem - navigateToMenu={ MENU_TEMPLATES_POSTS } - title={ __( 'Posts' ) } - /> - - <TemplateNavigationItems - templates={ generalTemplates } - onActivateItem={ onActivateItem } - /> + { search && ( + <SearchResults items={ templates } search={ search } /> + ) } - <TemplatePostsMenu - templates={ templates } - onActivateItem={ onActivateItem } - /> + { ! search && ( + <> + <NavigationItem + navigateToMenu={ MENU_TEMPLATES_ALL } + title={ _x( 'All', 'all templates' ) } + /> + <NavigationItem + navigateToMenu={ MENU_TEMPLATES_PAGES } + title={ __( 'Pages' ) } + hideIfTargetMenuEmpty + /> + <NavigationItem + navigateToMenu={ MENU_TEMPLATES_POSTS } + title={ __( 'Posts' ) } + hideIfTargetMenuEmpty + /> + { map( generalTemplates, ( template ) => ( + <TemplateNavigationItem + item={ template } + key={ `wp_template-${ template.id }` } + /> + ) ) } + </> + ) } - <TemplatesPagesMenu - templates={ templates } - onActivateItem={ onActivateItem } - /> + { ! search && templates === null && ( + <NavigationItem title={ __( 'Loading…' ) } isText /> + ) } - <TemplatesAllMenu - templates={ templates } - onActivateItem={ onActivateItem } - /> + <TemplatesPostsMenu templates={ templates } /> + <TemplatesPagesMenu templates={ templates } /> + <TemplatesAllMenu templates={ templates } /> </NavigationMenu> ); } diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/new-template-dropdown.js b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/new-template-dropdown.js new file mode 100644 index 00000000000000..c428472a051f2d --- /dev/null +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/new-template-dropdown.js @@ -0,0 +1,108 @@ +/** + * External dependencies + */ +import { filter, find, includes, map } from 'lodash'; + +/** + * WordPress dependencies + */ +import { + DropdownMenu, + MenuGroup, + MenuItem, + NavigableMenu, +} from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { Icon, plus } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import getClosestAvailableTemplate from '../../../utils/get-closest-available-template'; +import { TEMPLATES_NEW_OPTIONS, TEMPLATES_STATUSES } from './constants'; + +export default function NewTemplateDropdown() { + const { defaultTemplateTypes, templates } = useSelect( ( select ) => { + const { + __experimentalGetDefaultTemplateTypes: getDefaultTemplateTypes, + } = select( 'core/editor' ); + const templateEntities = select( 'core' ).getEntityRecords( + 'postType', + 'wp_template', + { status: TEMPLATES_STATUSES, per_page: -1 } + ); + return { + defaultTemplateTypes: getDefaultTemplateTypes(), + templates: templateEntities, + }; + }, [] ); + const { addTemplate } = useDispatch( 'core/edit-site' ); + + const createTemplate = ( slug ) => { + const closestAvailableTemplate = getClosestAvailableTemplate( + slug, + templates + ); + const { title, description } = find( defaultTemplateTypes, { slug } ); + addTemplate( { + content: closestAvailableTemplate.content.raw, + excerpt: description, + // Slugs need to be strings, so this is for template `404` + slug: slug.toString(), + status: 'draft', + title, + } ); + }; + + const existingTemplateSlugs = map( templates, 'slug' ); + + const missingTemplates = filter( + defaultTemplateTypes, + ( template ) => + includes( TEMPLATES_NEW_OPTIONS, template.slug ) && + ! includes( existingTemplateSlugs, template.slug ) + ); + + if ( ! missingTemplates.length ) { + return null; + } + + return ( + <DropdownMenu + className="edit-site-navigation-panel__new-template-dropdown" + icon={ null } + label={ __( 'Add Template' ) } + popoverProps={ { + noArrow: false, + } } + toggleProps={ { + children: <Icon icon={ plus } />, + isSmall: true, + isTertiary: true, + } } + > + { ( { onClose } ) => ( + <NavigableMenu className="edit-site-navigation-panel__new-template-popover"> + <MenuGroup label={ __( 'Add Template' ) }> + { map( + missingTemplates, + ( { title, description, slug } ) => ( + <MenuItem + info={ description } + key={ slug } + onClick={ () => { + createTemplate( slug ); + onClose(); + } } + > + { title } + </MenuItem> + ) + ) } + </MenuGroup> + </NavigableMenu> + ) } + </DropdownMenu> + ); +} diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/search-results.js b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/search-results.js new file mode 100644 index 00000000000000..39e7b60cb1781c --- /dev/null +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/search-results.js @@ -0,0 +1,73 @@ +/** + * External dependencies + */ +import { map } from 'lodash'; + +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; +import { __experimentalNavigationGroup as NavigationGroup } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { normalizedSearch } from './utils'; +import { useSelect } from '@wordpress/data'; +import TemplateNavigationItem from './template-navigation-item'; + +export default function SearchResults( { items, search } ) { + const itemType = items?.length > 0 ? items[ 0 ].type : null; + + const itemInfos = useSelect( + ( select ) => { + if ( itemType === 'wp_template' ) { + const { + __experimentalGetTemplateInfo: getTemplateInfo, + } = select( 'core/editor' ); + + return items.map( ( item ) => ( { + slug: item.slug, + ...getTemplateInfo( item ), + } ) ); + } + + return items.map( ( item ) => ( { + slug: item.slug, + title: item.title?.rendered, + description: item.excerpt?.rendered, + } ) ); + }, + [ items, itemType ] + ); + + const itemsFiltered = useMemo( () => { + if ( items === null || search.length === 0 ) { + return []; + } + + return items.filter( ( { slug } ) => { + const { title, description } = itemInfos.find( + ( info ) => info.slug === slug + ); + + return ( + normalizedSearch( slug, search ) || + normalizedSearch( title, search ) || + normalizedSearch( description, search ) + ); + } ); + }, [ items, itemInfos, search ] ); + + return ( + <NavigationGroup title={ __( 'Search results' ) }> + { map( itemsFiltered, ( item ) => ( + <TemplateNavigationItem + item={ item } + key={ `${ item.type }-${ item.id }` } + /> + ) ) } + </NavigationGroup> + ); +} diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/style.scss b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/style.scss index 916582a9de7c9a..d4fe4b544deca2 100644 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/style.scss +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/style.scss @@ -1,20 +1,56 @@ .edit-site-navigation-panel { - animation: edit-site-navigation-panel__slide-in 0.1s linear; height: 100%; - width: $nav-sidebar-width; + position: relative; + width: 0; + overflow: hidden; background: $gray-900; - @include reduce-motion("animation"); - - @keyframes edit-site-navigation-panel__slide-in { - from { - transform: translateX(-100%); - } - to { - transform: translateX(0%); - } + transition: width 100ms linear; + @include reduce-motion("transition"); + + // Footer is visible from medium so we subtract footer's height + @include break-medium() { + height: calc(100% - #{$button-size-small + $border-width}); } } +.edit-site-navigation-panel.is-open { + width: $nav-sidebar-width; +} + +.edit-site-navigation-panel__inner { + position: relative; + width: $nav-sidebar-width; + height: 100%; + overflow: hidden; +} + +.edit-site-navigation-panel__site-title-container { + height: $header-height; + padding-left: $header-height; + margin: 0 $grid-unit-20 0 $grid-unit-10; + display: flex; + align-items: center; +} + +.edit-site-navigation-panel__site-title { + font-style: normal; + font-weight: 600; + font-size: $default-font-size; + line-height: $default-line-height; + color: $gray-300; + + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.edit-site-navigation-panel__scroll-container { + overflow-x: hidden; + overflow-y: auto; + height: calc(100% - #{$header-height}); +} + .edit-site-navigation-panel__back-to-dashboard.components-button.is-tertiary { border-radius: 0; border-bottom: $border-width solid $gray-700; @@ -38,9 +74,10 @@ box-shadow: $shadow-popover; border-radius: $radius-block-ui; position: absolute; - top: $grid-unit-15; - left: calc(100% + #{$grid-unit-15}); + top: $header-height + $grid-unit-15 + 1px; // +1px for the header border + left: $nav-sidebar-width + $grid-unit-15; color: $gray-900; + z-index: z-index(".edit-site-navigation-panel__preview"); @include break-medium { display: block; @@ -50,11 +87,7 @@ .edit-site-navigation-panel__template-item { display: block; - &.is-active .edit-site-navigation-panel__template-item-description { - color: $gray-100; - } - - button { + .components-button { display: flex; flex-direction: column; align-items: flex-start; @@ -62,10 +95,33 @@ height: auto; min-height: $button-size; text-align: left; + padding-left: $grid-unit-20; + padding-right: $grid-unit-20; + margin-bottom: $grid-unit-15; + color: inherit; } } +.edit-site-navigation-panel__template-item-title { + em { + margin-right: 1ch; + } +} .edit-site-navigation-panel__template-item-description { padding-top: $grid-unit-05; - color: $gray-700; + font-size: 12px; + line-height: 16px; +} + +.edit-site-navigation-panel__new-template-dropdown { + margin: 0 0 0 $grid-unit-15; + + button { + margin: 0; + } +} +.edit-site-navigation-panel__new-template-popover { + @include break-small() { + min-width: 300px; + } } diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/template-navigation-item.js b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/template-navigation-item.js new file mode 100644 index 00000000000000..b63fc942368b9c --- /dev/null +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/template-navigation-item.js @@ -0,0 +1,67 @@ +/** + * WordPress dependencies + */ +import { + Button, + __experimentalNavigationItem as NavigationItem, +} from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import TemplatePreview from './template-preview'; +import { NavigationPanelPreviewFill } from '../index'; + +export default function TemplateNavigationItem( { item } ) { + const { title, description } = useSelect( + ( select ) => + 'wp_template' === item.type + ? select( 'core/editor' ).__experimentalGetTemplateInfo( item ) + : { title: item?.slug, description: '' }, + [] + ); + const { setTemplate, setTemplatePart } = useDispatch( 'core/edit-site' ); + const [ isPreviewVisible, setIsPreviewVisible ] = useState( false ); + + if ( ! item ) { + return null; + } + + const onActivateItem = () => + 'wp_template' === item.type + ? setTemplate( item.id ) + : setTemplatePart( item.id ); + + return ( + <NavigationItem + className="edit-site-navigation-panel__template-item" + item={ `${ item.type }-${ item.id }` } + title={ title } + > + <Button + onClick={ onActivateItem } + onMouseEnter={ () => setIsPreviewVisible( true ) } + onMouseLeave={ () => setIsPreviewVisible( false ) } + > + <div className="edit-site-navigation-panel__template-item-title"> + { 'draft' === item.status && <em>{ __( '[Draft]' ) }</em> } + { title } + </div> + { description && ( + <div className="edit-site-navigation-panel__template-item-description"> + { description } + </div> + ) } + </Button> + + { isPreviewVisible && ( + <NavigationPanelPreviewFill> + <TemplatePreview rawContent={ item.content.raw } /> + </NavigationPanelPreviewFill> + ) } + </NavigationItem> + ); +} diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/template-navigation-items.js b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/template-navigation-items.js deleted file mode 100644 index 48d88bda3f34d1..00000000000000 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/template-navigation-items.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * WordPress dependencies - */ -import { - Button, - __experimentalNavigationItem as NavigationItem, -} from '@wordpress/components'; -import { useState } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import TemplatePreview from './template-preview'; -import { NavigationPanelPreviewFill } from '.'; -import { getTemplateInfo } from '../../../utils'; - -export default function TemplateNavigationItems( { - entityType = 'wp_template', - templates, - onActivateItem, -} ) { - const [ hoveredTemplate, setHoveredTemplate ] = useState(); - - const onMouseEnterTemplate = ( template ) => setHoveredTemplate( template ); - const onMouseLeaveTemplate = () => setHoveredTemplate( null ); - - if ( ! templates ) { - return null; - } - - if ( ! Array.isArray( templates ) ) { - templates = [ templates ]; - } - - return ( - <> - { templates.map( ( template ) => { - const key = `${ entityType }-${ template.id }`; - const { title, description } = getTemplateInfo( template ); - return ( - <NavigationItem - className="edit-site-navigation-panel__template-item" - key={ key } - item={ key } - title={ title } - > - <Button - onClick={ () => onActivateItem( template.id ) } - onMouseEnter={ () => - onMouseEnterTemplate( template ) - } - onMouseLeave={ onMouseLeaveTemplate } - > - { title } - { description && ( - <div className="edit-site-navigation-panel__template-item-description"> - { description } - </div> - ) } - </Button> - </NavigationItem> - ); - } ) } - - { hoveredTemplate?.content?.raw && ( - <NavigationPanelPreviewFill> - <TemplatePreview - rawContent={ hoveredTemplate.content.raw } - /> - </NavigationPanelPreviewFill> - ) } - </> - ); -} diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/templates-navigation.js b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/templates-navigation.js index 82deddf0c4b399..55bcbf88439ea0 100644 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/templates-navigation.js +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/templates-navigation.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { useRef, useEffect } from '@wordpress/element'; import { __experimentalNavigation as Navigation, __experimentalNavigationMenu as NavigationMenu, @@ -16,17 +15,10 @@ import { __ } from '@wordpress/i18n'; */ import TemplatesMenu from './menus/templates'; import TemplatePartsMenu from './menus/template-parts'; +import MainDashboardButton from '../../main-dashboard-button'; import { MENU_ROOT, MENU_TEMPLATE_PARTS, MENU_TEMPLATES } from './constants'; export default function TemplatesNavigation() { - const ref = useRef(); - - useEffect( () => { - if ( ref.current ) { - ref.current.focus(); - } - }, [ ref ] ); - const { templateId, templatePartId, templateType, activeMenu } = useSelect( ( select ) => { const { @@ -46,11 +38,7 @@ export default function TemplatesNavigation() { [] ); - const { - setTemplate, - setTemplatePart, - setNavigationPanelActiveMenu, - } = useDispatch( 'core/edit-site' ); + const { setNavigationPanelActiveMenu } = useDispatch( 'core/edit-site' ); return ( <Navigation @@ -63,12 +51,13 @@ export default function TemplatesNavigation() { onActivateMenu={ setNavigationPanelActiveMenu } > { activeMenu === MENU_ROOT && ( - <NavigationBackButton - backButtonLabel={ __( 'Dashboard' ) } - className="edit-site-navigation-panel__back-to-dashboard" - href="index.php" - ref={ ref } - /> + <MainDashboardButton.Slot> + <NavigationBackButton + backButtonLabel={ __( 'Dashboard' ) } + className="edit-site-navigation-panel__back-to-dashboard" + href="index.php" + /> + </MainDashboardButton.Slot> ) } <NavigationMenu title={ __( 'Theme' ) }> @@ -82,9 +71,8 @@ export default function TemplatesNavigation() { navigateToMenu={ MENU_TEMPLATE_PARTS } /> - <TemplatesMenu onActivateItem={ setTemplate } /> - - <TemplatePartsMenu onActivateItem={ setTemplatePart } /> + <TemplatesMenu /> + <TemplatePartsMenu /> </NavigationMenu> </Navigation> ); diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/utils.js b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/utils.js new file mode 100644 index 00000000000000..251b90cfe44dfd --- /dev/null +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/utils.js @@ -0,0 +1,11 @@ +/** + * External dependencies + */ +import { deburr } from 'lodash'; + +// @see packages/block-editor/src/components/inserter/search-items.js +export const normalizeInput = ( input ) => + deburr( input ).replace( /^\//, '' ).toLowerCase(); + +export const normalizedSearch = ( title, search ) => + -1 !== normalizeInput( title ).indexOf( normalizeInput( search ) ); diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-toggle/index.js b/packages/edit-site/src/components/navigation-sidebar/navigation-toggle/index.js index 244c26b8ed785f..fc0a38f99fd1e6 100644 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-toggle/index.js +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-toggle/index.js @@ -7,29 +7,26 @@ import { __ } from '@wordpress/i18n'; import { wordpress } from '@wordpress/icons'; function NavigationToggle( { icon, isOpen } ) { - const { - isActive, - isRequestingSiteIcon, - siteIconUrl, - siteTitle, - } = useSelect( ( select ) => { - const { isFeatureActive } = select( 'core/edit-site' ); - const { getEntityRecord } = select( 'core' ); - const { isResolving } = select( 'core/data' ); - const siteData = - getEntityRecord( 'root', '__unstableBase', undefined ) || {}; + const { isActive, isRequestingSiteIcon, siteIconUrl } = useSelect( + ( select ) => { + const { isFeatureActive } = select( 'core/edit-site' ); + const { getEntityRecord } = select( 'core' ); + const { isResolving } = select( 'core/data' ); + const siteData = + getEntityRecord( 'root', '__unstableBase', undefined ) || {}; - return { - isActive: isFeatureActive( 'fullscreenMode' ), - isRequestingSiteIcon: isResolving( 'core', 'getEntityRecord', [ - 'root', - '__unstableBase', - undefined, - ] ), - siteIconUrl: siteData.site_icon_url, - siteTitle: siteData.name, - }; - }, [] ); + return { + isActive: isFeatureActive( 'fullscreenMode' ), + isRequestingSiteIcon: isResolving( 'core', 'getEntityRecord', [ + 'root', + '__unstableBase', + undefined, + ] ), + siteIconUrl: siteData.site_icon_url, + }; + }, + [] + ); const { setIsNavigationPanelOpened } = useDispatch( 'core/edit-site' ); @@ -67,12 +64,6 @@ function NavigationToggle( { icon, isOpen } ) { > { buttonIcon } </Button> - - { isOpen && ( - <div className="edit-site-navigation-toggle__site-title"> - { siteTitle } - </div> - ) } </div> ); } diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-toggle/style.scss b/packages/edit-site/src/components/navigation-sidebar/navigation-toggle/style.scss index c35c49ef8bb3fd..bd94f618b87669 100644 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-toggle/style.scss +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-toggle/style.scss @@ -1,40 +1,29 @@ .edit-site-navigation-toggle { + align-items: center; + background: $gray-900; + border-radius: 0; display: none; + position: absolute; + z-index: z-index(".edit-site-navigation-toggle"); + height: $header-height + $border-width; // Cover header border + width: $header-height; @include break-medium() { - align-items: center; - background: $gray-900; - border-radius: 0; display: flex; - position: absolute; - z-index: z-index(".edit-site-navigation-toggle"); - height: $header-height + $border-width; // Cover header border - width: $header-height; } -} - -.edit-site-navigation-toggle.is-open { - flex-shrink: 0; - height: $header-height + $border-width; // Cover header border - width: 300px; - position: static; - // Delay to sync with `NavigationPanel` animation - transition: width 80ms linear; - transition-delay: 20ms; - @include reduce-motion("transition"); - - .edit-site-navigation-toggle__button { - background: $gray-900; + body.is-navigation-sidebar-open & { + display: flex; } } .edit-site-navigation-toggle__button { align-items: center; - background: #23282e; // WP-admin gray. + background: $gray-900; color: $white; height: $header-height + $border-width; // Cover header border width: $header-height; + z-index: 1; &.has-icon { min-width: $header-height; @@ -55,17 +44,3 @@ .edit-site-navigation-toggle__site-icon { width: 36px; } - -.edit-site-navigation-toggle__site-title { - font-style: normal; - font-weight: 600; - font-size: $default-font-size; - line-height: $default-line-height; - color: $gray-300; - margin: 0 $grid-unit-20 0 $grid-unit-10; - - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; -} diff --git a/packages/edit-site/src/components/notices/index.js b/packages/edit-site/src/components/notices/index.js index 8243f4b13cf84a..5d053ad47eb68d 100644 --- a/packages/edit-site/src/components/notices/index.js +++ b/packages/edit-site/src/components/notices/index.js @@ -3,16 +3,17 @@ */ import { useSelect, useDispatch } from '@wordpress/data'; import { SnackbarList } from '@wordpress/components'; +import { store as noticesStore } from '@wordpress/notices'; export default function Notices() { const notices = useSelect( ( select ) => - select( 'core/notices' ) + select( noticesStore ) .getNotices() .filter( ( notice ) => notice.type === 'snackbar' ), [] ); - const { removeNotice } = useDispatch( 'core/notices' ); + const { removeNotice } = useDispatch( noticesStore ); return ( <SnackbarList className="edit-site-notices" diff --git a/packages/edit-site/src/components/save-button/index.js b/packages/edit-site/src/components/save-button/index.js index 38dd4467d689e6..4c8b4038ea3ca1 100644 --- a/packages/edit-site/src/components/save-button/index.js +++ b/packages/edit-site/src/components/save-button/index.js @@ -9,6 +9,7 @@ import { some } from 'lodash'; import { useSelect } from '@wordpress/data'; import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { useViewportMatch } from '@wordpress/compose'; export default function SaveButton( { openEntitiesSavedStates } ) { const { isDirty, isSaving } = useSelect( ( select ) => { @@ -26,6 +27,8 @@ export default function SaveButton( { openEntitiesSavedStates } ) { } ); const disabled = ! isDirty || isSaving; + const isMobile = useViewportMatch( 'medium', '<' ); + return ( <> <Button @@ -36,7 +39,7 @@ export default function SaveButton( { openEntitiesSavedStates } ) { isBusy={ isSaving } onClick={ disabled ? undefined : openEntitiesSavedStates } > - { __( 'Update Design' ) } + { isMobile ? __( 'Update' ) : __( 'Update Design' ) } </Button> </> ); diff --git a/packages/edit-site/src/components/sidebar/color-palette-panel.js b/packages/edit-site/src/components/sidebar/color-palette-panel.js index a538a6c21d37ae..8ea1d543875633 100644 --- a/packages/edit-site/src/components/sidebar/color-palette-panel.js +++ b/packages/edit-site/src/components/sidebar/color-palette-panel.js @@ -1,40 +1,73 @@ +/** + * External dependencies + */ +import { get } from 'lodash'; + /** * WordPress dependencies */ -import { - Button, - __experimentalColorEdit as ColorEdit, -} from '@wordpress/components'; +import { __experimentalColorEdit as ColorEdit } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { useEditorFeature, GLOBAL_CONTEXT_NAME } from '../editor/utils'; -export default ( { contextName, getSetting, setSetting } ) => { - const colors = getSetting( contextName, 'color.palette' ); - let emptyUI; - if ( colors === undefined ) { - emptyUI = __( - 'Using theme or core default colors. Add some colors to create your own color palette instead.' - ); - } else if ( colors && colors.length === 0 ) { - emptyUI = ( - <> - <p>{ __( 'Using an empty color palette.' ) }</p> - <Button - isSmall - isSecondary - onClick={ () => setSetting( contextName, 'color.palette' ) } - > - { __( 'Reset to theme/core defaults' ) } - </Button> - </> - ); - } +/** + * Shared reference to an empty array for cases where it is important to avoid + * returning a new array reference on every invocation, as in a connected or + * other pure component which performs `shouldComponentUpdate` check on props. + * This should be used as a last resort, since the normalized data should be + * maintained by the reducer result in state. + * + * @type {Array} + */ +const EMPTY_ARRAY = []; + +export default function ColorPalettePanel( { + contextName, + getSetting, + setSetting, +} ) { + const colors = useEditorFeature( 'color.palette', contextName ); + const userColors = getSetting( contextName, 'color.palette' ); + const immutableColorSlugs = useSelect( + ( select ) => { + const baseStyles = select( 'core/edit-site' ).getSettings() + .__experimentalGlobalStylesBaseStyles; + const basePalette = + get( baseStyles, [ + contextName, + 'settings', + 'color', + 'palette', + ] ) ?? + get( baseStyles, [ + GLOBAL_CONTEXT_NAME, + 'settings', + 'color', + 'palette', + ] ); + if ( ! basePalette ) { + return EMPTY_ARRAY; + } + return basePalette.map( ( { slug } ) => slug ); + }, + [ contextName ] + ); return ( <ColorEdit + immutableColorSlugs={ immutableColorSlugs } colors={ colors } onChange={ ( newColors ) => { setSetting( contextName, 'color.palette', newColors ); } } - emptyUI={ emptyUI } + emptyUI={ __( + 'Colors are empty! Add some colors to create your own color palette.' + ) } + canReset={ colors === userColors } /> ); -}; +} diff --git a/packages/edit-site/src/components/sidebar/color-panel.js b/packages/edit-site/src/components/sidebar/color-panel.js index fba377bcf5b250..9960ab1d7b62b2 100644 --- a/packages/edit-site/src/components/sidebar/color-panel.js +++ b/packages/edit-site/src/components/sidebar/color-panel.js @@ -7,52 +7,78 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { LINK_COLOR } from '../editor/utils'; +import { LINK_COLOR, useEditorFeature } from '../editor/utils'; import ColorPalettePanel from './color-palette-panel'; -export default ( { +export function useHasColorPanel( { supports } ) { + return ( + supports.includes( 'color' ) || + supports.includes( 'backgroundColor' ) || + supports.includes( 'background' ) || + supports.includes( LINK_COLOR ) + ); +} + +export default function ColorPanel( { context: { supports, name }, getStyleProperty, setStyleProperty, getSetting, setSetting, -} ) => { - if ( - ! supports.includes( 'color' ) && - ! supports.includes( 'backgrounColor' ) && - ! supports.includes( 'background' ) && - ! supports.includes( LINK_COLOR ) - ) { - return null; - } +} ) { + const colors = useEditorFeature( 'color.palette', name ); + const disableCustomColors = ! useEditorFeature( 'color.custom', name ); + const gradients = useEditorFeature( 'color.gradients', name ); + const disableCustomGradients = ! useEditorFeature( + 'color.customGradient', + name + ); const settings = []; if ( supports.includes( 'color' ) ) { + const color = getStyleProperty( name, 'color' ); + const userColor = getStyleProperty( name, 'color', 'user' ); settings.push( { - colorValue: getStyleProperty( name, 'color' ), + colorValue: color, onColorChange: ( value ) => setStyleProperty( name, 'color', value ), label: __( 'Text color' ), + clearable: color === userColor, } ); } let backgroundSettings = {}; if ( supports.includes( 'backgroundColor' ) ) { + const backgroundColor = getStyleProperty( name, 'backgroundColor' ); + const userBackgroundColor = getStyleProperty( + name, + 'backgroundColor', + 'user' + ); backgroundSettings = { - colorValue: getStyleProperty( name, 'backgroundColor' ), + colorValue: backgroundColor, onColorChange: ( value ) => setStyleProperty( name, 'backgroundColor', value ), }; + if ( backgroundColor ) { + backgroundSettings.clearable = + backgroundColor === userBackgroundColor; + } } let gradientSettings = {}; if ( supports.includes( 'background' ) ) { + const gradient = getStyleProperty( name, 'background' ); + const userGradient = getStyleProperty( name, 'background', 'user' ); gradientSettings = { - gradientValue: getStyleProperty( name, 'background' ), + gradientValue: gradient, onGradientChange: ( value ) => setStyleProperty( name, 'background', value ), }; + if ( gradient ) { + gradientSettings.clearable = gradient === userGradient; + } } if ( @@ -67,18 +93,24 @@ export default ( { } if ( supports.includes( LINK_COLOR ) ) { + const color = getStyleProperty( name, LINK_COLOR ); + const userColor = getStyleProperty( name, LINK_COLOR, 'user' ); settings.push( { - colorValue: getStyleProperty( name, LINK_COLOR ), + colorValue: color, onColorChange: ( value ) => setStyleProperty( name, LINK_COLOR, value ), label: __( 'Link color' ), + clearable: color === userColor, } ); } - return ( <PanelColorGradientSettings title={ __( 'Color' ) } settings={ settings } + colors={ colors } + gradients={ gradients } + disableCustomColors={ disableCustomColors } + disableCustomGradients={ disableCustomGradients } > <ColorPalettePanel key={ 'color-palette-panel-' + name } @@ -88,4 +120,4 @@ export default ( { /> </PanelColorGradientSettings> ); -}; +} diff --git a/packages/edit-site/src/components/sidebar/default-sidebar.js b/packages/edit-site/src/components/sidebar/default-sidebar.js index 2f8c21202fb0d6..ad2cf7ca8284a2 100644 --- a/packages/edit-site/src/components/sidebar/default-sidebar.js +++ b/packages/edit-site/src/components/sidebar/default-sidebar.js @@ -6,7 +6,7 @@ import { ComplementaryAreaMoreMenuItem, } from '@wordpress/interface'; -export default ( { +export default function DefaultSidebar( { className, identifier, title, @@ -14,7 +14,7 @@ export default ( { children, closeLabel, header, -} ) => { +} ) { return ( <> <ComplementaryArea @@ -37,4 +37,4 @@ export default ( { </ComplementaryAreaMoreMenuItem> </> ); -}; +} diff --git a/packages/edit-site/src/components/sidebar/global-styles-sidebar.js b/packages/edit-site/src/components/sidebar/global-styles-sidebar.js index 1b94f8db84674e..d2308264f2ca90 100644 --- a/packages/edit-site/src/components/sidebar/global-styles-sidebar.js +++ b/packages/edit-site/src/components/sidebar/global-styles-sidebar.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { omit } from 'lodash'; +import { map, sortBy } from 'lodash'; /** * WordPress dependencies @@ -9,6 +9,7 @@ import { omit } from 'lodash'; import { Button, PanelBody, TabPanel } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { getBlockType } from '@wordpress/blocks'; +import { useMemo } from '@wordpress/element'; /** * Internal dependencies @@ -18,11 +19,130 @@ import { useGlobalStylesReset, } from '../editor/global-styles-provider'; import DefaultSidebar from './default-sidebar'; -import { GLOBAL_CONTEXT } from '../editor/utils'; -import TypographyPanel from './typography-panel'; -import ColorPanel from './color-panel'; +import { GLOBAL_CONTEXT_NAME } from '../editor/utils'; +import { + default as TypographyPanel, + useHasTypographyPanel, +} from './typography-panel'; +import { default as ColorPanel, useHasColorPanel } from './color-panel'; + +function GlobalStylesPanel( { + wrapperPanelTitle, + context, + getStyleProperty, + setStyleProperty, + getSetting, + setSetting, +} ) { + const hasColorPanel = useHasColorPanel( context ); + const hasTypographyPanel = useHasTypographyPanel( context ); + + if ( ! hasColorPanel && ! hasTypographyPanel ) { + return null; + } + + const content = ( + <> + { hasTypographyPanel && ( + <TypographyPanel + context={ context } + getStyleProperty={ getStyleProperty } + setStyleProperty={ setStyleProperty } + /> + ) } + { hasColorPanel && ( + <ColorPanel + context={ context } + getStyleProperty={ getStyleProperty } + setStyleProperty={ setStyleProperty } + getSetting={ getSetting } + setSetting={ setSetting } + /> + ) } + </> + ); + if ( ! wrapperPanelTitle ) { + return content; + } + return ( + <PanelBody title={ wrapperPanelTitle } initialOpen={ false }> + { content } + </PanelBody> + ); +} + +function getPanelTitle( context ) { + /* + * We use the block's name as the panel title. + * + * Some blocks (eg: core/heading) can represent different + * contexts (eg: core/heading/h1, core/heading/h2). + * For those, we attach the selector (h1) after the block's name. + * + * The title can't be accessed in the server, + * as it's translatable and the block.json doesn't + * have it yet. + */ + const blockType = getBlockType( context.blockName ); + // Protect against blocks that aren't registered + // eg: widget-area + if ( blockType === undefined ) { + return blockType; + } + + let panelTitle = blockType.title; + if ( 'object' === typeof blockType?.supports?.__experimentalSelector ) { + panelTitle += ` (${ context.title })`; + } + return panelTitle; +} + +function GlobalStylesBlockPanels( { + contexts, + getStyleProperty, + setStyleProperty, + getSetting, + setSetting, +} ) { + const panels = useMemo( + () => + sortBy( + map( contexts, ( context, name ) => { + return { + context, + name, + wrapperPanelTitle: getPanelTitle( context ), + }; + } ), + ( { wrapperPanelTitle } ) => wrapperPanelTitle + ), + [ contexts ] + ); + + return map( panels, ( { context, name, wrapperPanelTitle } ) => { + if ( name === GLOBAL_CONTEXT_NAME ) { + return null; + } + return ( + <GlobalStylesPanel + key={ 'panel-' + name } + wrapperPanelTitle={ wrapperPanelTitle } + context={ { ...context, name } } + getStyleProperty={ getStyleProperty } + setStyleProperty={ setStyleProperty } + getSetting={ getSetting } + setSetting={ setSetting } + /> + ); + } ); +} -export default ( { identifier, title, icon, closeLabel } ) => { +export default function GlobalStylesSidebar( { + identifier, + title, + icon, + closeLabel, +} ) { const { contexts, getStyleProperty, @@ -32,7 +152,7 @@ export default ( { identifier, title, icon, closeLabel } ) => { } = useGlobalStylesContext(); const [ canRestart, onReset ] = useGlobalStylesReset(); - if ( typeof contexts !== 'object' || ! contexts?.[ GLOBAL_CONTEXT ] ) { + if ( typeof contexts !== 'object' || ! contexts?.[ GLOBAL_CONTEXT_NAME ] ) { // No sidebar is shown. return null; } @@ -68,114 +188,31 @@ export default ( { identifier, title, icon, closeLabel } ) => { { ( tab ) => { /* Per Block Context */ if ( 'block' === tab.name ) { - return Object.keys( - omit( contexts, [ GLOBAL_CONTEXT ] ) - ) - .map( ( name ) => { - const { - supports, - selector, - blockName, - } = contexts[ name ]; - - /* - * We use the block's name as the panel title. - * - * Some blocks (eg: core/heading) can represent different - * contexts (eg: core/heading/h1, core/heading/h2). - * For those, we attach the selector (h1) after the block's name. - * - * The title can't be accessed in the server, - * as it's translatable and the block.json doesn't - * have it yet. - */ - - const blockType = getBlockType( blockName ); - // Protect against blocks that aren't registered - // eg: widget-area - if ( blockType === undefined ) { - return blockType; - } - - let panelTitle = blockType.title; - if ( - 'object' === - typeof blockType?.supports - ?.__experimentalSelector - ) { - panelTitle += ` (${ selector })`; - } - - return ( - <PanelBody - key={ 'panel-' + name } - title={ panelTitle } - initialOpen={ false } - > - { [ - <TypographyPanel - key={ - 'typography-panel-' + name - } - context={ { - supports, - name, - } } - getStyleProperty={ - getStyleProperty - } - setStyleProperty={ - setStyleProperty - } - />, - <ColorPanel - key={ 'color-panel-' + name } - context={ { - supports, - name, - } } - getStyleProperty={ - getStyleProperty - } - setStyleProperty={ - setStyleProperty - } - getSetting={ getSetting } - setSetting={ setSetting } - />, - ].filter( Boolean ) } - </PanelBody> - ); - } ) - .filter( Boolean ); + return ( + <GlobalStylesBlockPanels + contexts={ contexts } + getStyleProperty={ getStyleProperty } + setStyleProperty={ setStyleProperty } + getSetting={ getSetting } + setSetting={ setSetting } + /> + ); } - - /* Global Context */ - const { supports, blockName } = contexts[ GLOBAL_CONTEXT ]; - return [ - <TypographyPanel - key={ 'typography-panel-' + blockName } - context={ { - supports, - name: blockName, - } } - getStyleProperty={ getStyleProperty } - setStyleProperty={ setStyleProperty } - />, - <ColorPanel - key={ 'color-panel-' + blockName } + return ( + <GlobalStylesPanel + hasWrapper={ false } context={ { - supports, - name: blockName, + ...contexts[ GLOBAL_CONTEXT_NAME ], + name: GLOBAL_CONTEXT_NAME, } } getStyleProperty={ getStyleProperty } setStyleProperty={ setStyleProperty } getSetting={ getSetting } setSetting={ setSetting } - />, - ].filter( Boolean ); + /> + ); } } </TabPanel> </DefaultSidebar> ); -}; +} diff --git a/packages/edit-site/src/components/sidebar/typography-panel.js b/packages/edit-site/src/components/sidebar/typography-panel.js index d753c8efbe2c1c..529115d8b5adf5 100644 --- a/packages/edit-site/src/components/sidebar/typography-panel.js +++ b/packages/edit-site/src/components/sidebar/typography-panel.js @@ -1,38 +1,86 @@ /** * WordPress dependencies */ -import { FontSizePicker, LineHeightControl } from '@wordpress/block-editor'; -import { PanelBody } from '@wordpress/components'; +import { + LineHeightControl, + __experimentalFontFamilyControl as FontFamilyControl, + __experimentalFontAppearanceControl as FontAppearanceControl, +} from '@wordpress/block-editor'; +import { PanelBody, FontSizePicker } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { fromPx, toPx } from '../editor/utils'; +import { useEditorFeature } from '../editor/utils'; -export default ( { +export function useHasTypographyPanel( { supports, name } ) { + const hasLineHeight = useHasLineHeightControl( { supports, name } ); + const hasFontAppearence = useHasAppearenceControl( { supports, name } ); + return ( + hasLineHeight || hasFontAppearence || supports.includes( 'fontSize' ) + ); +} + +function useHasLineHeightControl( { supports, name } ) { + return ( + useEditorFeature( 'typography.customLineHeight', name ) && + supports.includes( 'lineHeight' ) + ); +} + +function useHasAppearenceControl( { supports, name } ) { + const hasFontStyles = + useEditorFeature( 'typography.customFontStyle', name ) && + supports.includes( 'fontStyle' ); + const hasFontWeights = + useEditorFeature( 'typography.customFontWeight', name ) && + supports.includes( 'fontWeight' ); + return hasFontStyles || hasFontWeights; +} + +export default function TypographyPanel( { context: { supports, name }, getStyleProperty, setStyleProperty, -} ) => { - if ( - ! supports.includes( 'fontSize' ) && - ! supports.includes( 'lineHeight' ) - ) { - return null; - } +} ) { + const fontSizes = useEditorFeature( 'typography.fontSizes', name ); + const disableCustomFontSizes = ! useEditorFeature( + 'typography.customFontSize', + name + ); + const fontFamilies = useEditorFeature( 'typography.fontFamilies', name ); + const hasFontStyles = + useEditorFeature( 'typography.customFontStyle', name ) && + supports.includes( 'fontStyle' ); + const hasFontWeights = + useEditorFeature( 'typography.customFontWeight', name ) && + supports.includes( 'fontWeight' ); + const hasLineHeightEnabled = useHasLineHeightControl( { supports, name } ); + const hasAppearenceControl = useHasAppearenceControl( { supports, name } ); return ( <PanelBody title={ __( 'Typography' ) } initialOpen={ true }> + { supports.includes( 'fontFamily' ) && ( + <FontFamilyControl + fontFamilies={ fontFamilies } + value={ getStyleProperty( name, 'fontFamily' ) } + onChange={ ( value ) => + setStyleProperty( name, 'fontFamily', value ) + } + /> + ) } { supports.includes( 'fontSize' ) && ( <FontSizePicker - value={ fromPx( getStyleProperty( name, 'fontSize' ) ) } + value={ getStyleProperty( name, 'fontSize' ) } onChange={ ( value ) => - setStyleProperty( name, 'fontSize', toPx( value ) ) + setStyleProperty( name, 'fontSize', value ) } + fontSizes={ fontSizes } + disableCustomFontSizes={ disableCustomFontSizes } /> ) } - { supports.includes( 'lineHeight' ) && ( + { hasLineHeightEnabled && ( <LineHeightControl value={ getStyleProperty( name, 'lineHeight' ) } onChange={ ( value ) => @@ -40,6 +88,20 @@ export default ( { } /> ) } + { hasAppearenceControl && ( + <FontAppearanceControl + value={ { + fontStyle: getStyleProperty( name, 'fontStyle' ), + fontWeight: getStyleProperty( name, 'fontWeight' ), + } } + onChange={ ( { fontStyle, fontWeight } ) => { + setStyleProperty( name, 'fontStyle', fontStyle ); + setStyleProperty( name, 'fontWeight', fontWeight ); + } } + hasFontStyles={ hasFontStyles } + hasFontWeights={ hasFontWeights } + /> + ) } </PanelBody> ); -}; +} diff --git a/packages/edit-site/src/components/template-details/index.js b/packages/edit-site/src/components/template-details/index.js index a8bcee06279078..ae2d1ab4f01577 100644 --- a/packages/edit-site/src/components/template-details/index.js +++ b/packages/edit-site/src/components/template-details/index.js @@ -3,22 +3,25 @@ */ import { __, sprintf } from '@wordpress/i18n'; import { Button, __experimentalText as Text } from '@wordpress/components'; -import { useDispatch } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; /** * Internal dependencies */ -import { getTemplateInfo } from '../../utils'; import { MENU_TEMPLATES } from '../navigation-sidebar/navigation-panel/constants'; export default function TemplateDetails( { template, onClose } ) { + const { title, description } = useSelect( + ( select ) => + select( 'core/editor' ).__experimentalGetTemplateInfo( template ), + [] + ); const { openNavigationPanelToMenu } = useDispatch( 'core/edit-site' ); + if ( ! template ) { return null; } - const { title, description } = getTemplateInfo( template ); - const showTemplateInSidebar = () => { onClose(); openNavigationPanelToMenu( MENU_TEMPLATES ); diff --git a/packages/edit-site/src/components/template-part-converter/convert-to-regular.js b/packages/edit-site/src/components/template-part-converter/convert-to-regular.js new file mode 100644 index 00000000000000..b5151ee554d54a --- /dev/null +++ b/packages/edit-site/src/components/template-part-converter/convert-to-regular.js @@ -0,0 +1,33 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { BlockSettingsMenuControls } from '@wordpress/block-editor'; +import { MenuItem } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +export default function ConvertToRegularBlocks( { clientId } ) { + const { innerBlocks } = useSelect( + ( select ) => + select( 'core/block-editor' ).__unstableGetBlockWithBlockTree( + clientId + ), + [ clientId ] + ); + const { replaceBlocks } = useDispatch( 'core/block-editor' ); + + return ( + <BlockSettingsMenuControls> + { ( { onClose } ) => ( + <MenuItem + onClick={ () => { + replaceBlocks( clientId, innerBlocks ); + onClose(); + } } + > + { __( 'Detach blocks from template part' ) } + </MenuItem> + ) } + </BlockSettingsMenuControls> + ); +} diff --git a/packages/edit-site/src/components/template-part-converter/convert-to-template-part.js b/packages/edit-site/src/components/template-part-converter/convert-to-template-part.js new file mode 100644 index 00000000000000..0e8c4e0a3d24d8 --- /dev/null +++ b/packages/edit-site/src/components/template-part-converter/convert-to-template-part.js @@ -0,0 +1,40 @@ +/** + * WordPress dependencies + */ +import { useDispatch } from '@wordpress/data'; +import { BlockSettingsMenuControls } from '@wordpress/block-editor'; +import { MenuItem } from '@wordpress/components'; +import { createBlock } from '@wordpress/blocks'; +import { __ } from '@wordpress/i18n'; + +export default function ConvertToTemplatePart( { clientIds, blocks } ) { + const { replaceBlocks } = useDispatch( 'core/block-editor' ); + + return ( + <BlockSettingsMenuControls> + { ( { onClose } ) => ( + <MenuItem + onClick={ () => { + replaceBlocks( + clientIds, + createBlock( + 'core/template-part', + {}, + blocks.map( ( block ) => + createBlock( + block.name, + block.attributes, + block.innerBlocks + ) + ) + ) + ); + onClose(); + } } + > + { __( 'Make template part' ) } + </MenuItem> + ) } + </BlockSettingsMenuControls> + ); +} diff --git a/packages/edit-site/src/components/template-part-converter/index.js b/packages/edit-site/src/components/template-part-converter/index.js new file mode 100644 index 00000000000000..ae018a555cccb2 --- /dev/null +++ b/packages/edit-site/src/components/template-part-converter/index.js @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import ConvertToRegularBlocks from './convert-to-regular'; +import ConvertToTemplatePart from './convert-to-template-part'; + +export default function TemplatePartConverter() { + const { clientIds, blocks } = useSelect( ( select ) => { + const { getSelectedBlockClientIds, getBlocksByClientId } = select( + 'core/block-editor' + ); + const selectedBlockClientIds = getSelectedBlockClientIds(); + return { + clientIds: selectedBlockClientIds, + blocks: getBlocksByClientId( selectedBlockClientIds ), + }; + } ); + + // Allow converting a single template part to standard blocks. + if ( blocks.length === 1 && blocks[ 0 ]?.name === 'core/template-part' ) { + return <ConvertToRegularBlocks clientId={ clientIds[ 0 ] } />; + } + + return <ConvertToTemplatePart clientIds={ clientIds } blocks={ blocks } />; +} diff --git a/packages/edit-site/src/components/url-query-controller/index.js b/packages/edit-site/src/components/url-query-controller/index.js new file mode 100644 index 00000000000000..e7685a1f1ab13a --- /dev/null +++ b/packages/edit-site/src/components/url-query-controller/index.js @@ -0,0 +1,80 @@ +/** + * WordPress dependencies + */ +import { useEffect } from '@wordpress/element'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { getQueryArg, addQueryArgs, removeQueryArgs } from '@wordpress/url'; + +export default function URLQueryController() { + const { setTemplate, setTemplatePart, showHomepage, setPage } = useDispatch( + 'core/edit-site' + ); + + // Set correct entity on load. + useEffect( () => { + const url = window.location.href; + const postId = getQueryArg( url, 'postId' ); + + if ( ! postId ) { + showHomepage(); + return; + } + + const postType = getQueryArg( url, 'postType' ); + if ( 'page' === postType || 'post' === postType ) { + setPage( { context: { postType, postId } } ); // Resolves correct template based on ID. + } else if ( 'wp_template' === postType ) { + setTemplate( postId ); + } else if ( 'wp_template_part' === postType ) { + setTemplatePart( postId ); + } else { + showHomepage(); + } + }, [] ); + + // Update page URL when context changes. + const pageContext = useCurrentPageContext(); + useEffect( () => { + const newUrl = pageContext + ? addQueryArgs( window.location.href, pageContext ) + : removeQueryArgs( window.location.href, 'postType', 'postId' ); + + window.history.replaceState( {}, '', newUrl ); + }, [ pageContext ] ); + + return null; +} + +function useCurrentPageContext() { + return useSelect( ( select ) => { + const { + getTemplateId, + getTemplatePartId, + getTemplateType, + getPage, + } = select( 'core/edit-site' ); + + const page = getPage(); + const templateType = getTemplateType(); + const templateId = getTemplateId(); + const templatePartId = getTemplatePartId(); + + let _postId, _postType; + if ( page?.context?.postId && page?.context?.postType ) { + _postId = page.context.postId; + _postType = page.context.postType; + } else if ( templateType === 'wp_template' && templateId ) { + _postId = templateId; + _postType = templateType; + } else if ( templateType === 'wp_template_part' && templatePartId ) { + _postId = templatePartId; + _postType = templateType; + } + + if ( _postId && _postType ) { + return { postId: _postId, postType: _postType }; + } + + return null; + } ); +} diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js index 7c040442e9b872..9f5bb993511460 100644 --- a/packages/edit-site/src/index.js +++ b/packages/edit-site/src/index.js @@ -4,7 +4,6 @@ import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; import { __ } from '@wordpress/i18n'; -import '@wordpress/notices'; import { registerCoreBlocks, __experimentalRegisterExperimentalCoreBlocks, @@ -61,11 +60,11 @@ export function initialize( id, settings ) { registerCoreBlocks(); if ( process.env.GUTENBERG_PHASE === 2 ) { - __experimentalRegisterExperimentalCoreBlocks( settings ); + __experimentalRegisterExperimentalCoreBlocks( true ); } render( <Editor />, document.getElementById( id ) ); } -export { default as __experimentalFullscreenModeClose } from './components/header/fullscreen-mode-close'; +export { default as __experimentalMainDashboardButton } from './components/main-dashboard-button'; export { default as __experimentalNavigationToggle } from './components/navigation-sidebar/navigation-toggle'; diff --git a/packages/edit-site/src/store/actions.js b/packages/edit-site/src/store/actions.js index 2272278608fc3e..3c3031f539d432 100644 --- a/packages/edit-site/src/store/actions.js +++ b/packages/edit-site/src/store/actions.js @@ -1,7 +1,8 @@ /** * WordPress dependencies */ -import { select, dispatch, apiFetch } from '@wordpress/data-controls'; +import { controls } from '@wordpress/data'; +import { apiFetch } from '@wordpress/data-controls'; /** * Internal dependencies @@ -58,7 +59,7 @@ export function setTemplate( templateId ) { * @return {Object} Action object used to set the current template. */ export function* addTemplate( template ) { - const newTemplate = yield dispatch( + const newTemplate = yield controls.dispatch( 'core', 'saveEntityRecord', 'postType', @@ -81,11 +82,8 @@ export function* removeTemplate( templateId ) { path: `/wp/v2/templates/${ templateId }`, method: 'DELETE', } ); - yield dispatch( - 'core/edit-site', - 'setPage', - yield select( 'core/edit-site', 'getPage' ) - ); + const page = yield controls.select( 'core/edit-site', 'getPage' ); + yield controls.dispatch( 'core/edit-site', 'setPage', page ); } /** @@ -116,7 +114,8 @@ export function setHomeTemplateId( homeTemplateId ) { } /** - * Resolves the template for a page and displays both. + * Resolves the template for a page and displays both. If no path is given, attempts + * to use the postId to generate a path like `?p=${ postId }`. * * @param {Object} page The page object. * @param {string} page.type The page type. @@ -127,6 +126,9 @@ export function setHomeTemplateId( homeTemplateId ) { * @return {number} The resolved template ID for the page route. */ export function* setPage( page ) { + if ( ! page.path && page.context?.postId ) { + page.path = `?p=${ page.context.postId }`; + } const templateId = yield findTemplate( page.path ); yield { type: 'SET_PAGE', @@ -143,7 +145,12 @@ export function* showHomepage() { const { show_on_front: showOnFront, page_on_front: frontpageId, - } = yield select( 'core', 'getEntityRecord', 'root', 'site' ); + } = yield controls.resolveSelect( + 'core', + 'getEntityRecord', + 'root', + 'site' + ); const page = { path: '/', diff --git a/packages/edit-site/src/store/constants.js b/packages/edit-site/src/store/constants.js index 27b051950e603b..174341a5cb02ae 100644 --- a/packages/edit-site/src/store/constants.js +++ b/packages/edit-site/src/store/constants.js @@ -3,4 +3,4 @@ * * @type {string} */ -export const STORE_KEY = 'core/edit-site'; +export const STORE_NAME = 'core/edit-site'; diff --git a/packages/edit-site/src/store/index.js b/packages/edit-site/src/store/index.js index fa012273201cde..2fa2e95eb1dc7e 100644 --- a/packages/edit-site/src/store/index.js +++ b/packages/edit-site/src/store/index.js @@ -11,10 +11,10 @@ import reducer from './reducer'; import * as actions from './actions'; import * as selectors from './selectors'; import controls from './controls'; -import { STORE_KEY } from './constants'; +import { STORE_NAME } from './constants'; export default function registerEditSiteStore( initialState ) { - const store = registerStore( STORE_KEY, { + const store = registerStore( STORE_NAME, { reducer, actions, selectors, @@ -23,7 +23,5 @@ export default function registerEditSiteStore( initialState ) { initialState, } ); - store.dispatch( actions.showHomepage() ); - return store; } diff --git a/packages/edit-site/src/store/reducer.js b/packages/edit-site/src/store/reducer.js index a45c0229a29f1a..dcc57c8bc3a9af 100644 --- a/packages/edit-site/src/store/reducer.js +++ b/packages/edit-site/src/store/reducer.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { flow } from 'lodash'; - /** * WordPress dependencies */ @@ -14,38 +9,25 @@ import { combineReducers } from '@wordpress/data'; import { PREFERENCES_DEFAULTS } from './defaults'; import { MENU_ROOT } from '../components/navigation-sidebar/navigation-panel/constants'; -/** - * Higher-order reducer creator which provides the given initial state for the - * original reducer. - * - * @param {*} initialState Initial state to provide to reducer. - * - * @return {Function} Higher-order reducer. - */ -const createWithInitialState = ( initialState ) => ( reducer ) => { - return ( state = initialState, action ) => reducer( state, action ); -}; - /** * Reducer returning the user preferences. * * @param {Object} state Current state. - * + * @param {Object} action Dispatched action. * @return {Object} Updated state. */ -export const preferences = flow( [ - combineReducers, - createWithInitialState( PREFERENCES_DEFAULTS ), -] )( { - features( state, action ) { - if ( action.type === 'TOGGLE_FEATURE' ) { - return { - ...state, - [ action.feature ]: ! state[ action.feature ], - }; +export const preferences = combineReducers( { + features( state = PREFERENCES_DEFAULTS.features, action ) { + switch ( action.type ) { + case 'TOGGLE_FEATURE': { + return { + ...state, + [ action.feature ]: ! state[ action.feature ], + }; + } + default: + return state; } - - return state; }, } ); @@ -99,6 +81,8 @@ export function templateId( state, action ) { case 'SET_TEMPLATE': case 'SET_PAGE': return action.templateId; + case 'SET_TEMPLATE_PART': + return undefined; } return state; @@ -116,6 +100,9 @@ export function templatePartId( state, action ) { switch ( action.type ) { case 'SET_TEMPLATE_PART': return action.templatePartId; + case 'SET_TEMPLATE': + case 'SET_PAGE': + return undefined; } return state; @@ -153,6 +140,8 @@ export function page( state, action ) { switch ( action.type ) { case 'SET_PAGE': return action.page; + case 'SET_TEMPLATE_PART': + return undefined; } return state; diff --git a/packages/edit-site/src/store/selectors.js b/packages/edit-site/src/store/selectors.js index 36509472c1a5b1..2fc2ba5d43614f 100644 --- a/packages/edit-site/src/store/selectors.js +++ b/packages/edit-site/src/store/selectors.js @@ -56,6 +56,7 @@ export const getSettings = createSelector( ( state, setIsInserterOpen ) => { const settings = { ...state.settings, + outlineMode: true, focusMode: isFeatureActive( state, 'focusMode' ), hasFixedToolbar: isFeatureActive( state, 'fixedToolbar' ), __experimentalSetIsInserterOpened: setIsInserterOpen, diff --git a/packages/edit-site/src/store/test/actions.js b/packages/edit-site/src/store/test/actions.js index 5dd56d9e091ef9..f4929aace6d349 100644 --- a/packages/edit-site/src/store/test/actions.js +++ b/packages/edit-site/src/store/test/actions.js @@ -56,7 +56,7 @@ describe( 'actions', () => { } ); describe( 'removeTemplate', () => { - it( 'should yield the API_FETCH control and yield the SELECT control to set the page by yielding the DISPATCH control for the SET_PAGE action', () => { + it( 'should issue a REST request to delete the template, then read the current page and then set the page with an updated template list', () => { const templateId = 1; const page = { path: '/' }; @@ -69,7 +69,7 @@ describe( 'actions', () => { }, } ); expect( it.next().value ).toEqual( { - type: '@@data/RESOLVE_SELECT', + type: '@@data/SELECT', storeKey: 'core/edit-site', selectorName: 'getPage', args: [], diff --git a/packages/edit-site/src/store/test/selectors.js b/packages/edit-site/src/store/test/selectors.js index 7a187e99d7831d..85b4ac71219d86 100644 --- a/packages/edit-site/src/store/test/selectors.js +++ b/packages/edit-site/src/store/test/selectors.js @@ -83,6 +83,7 @@ describe( 'selectors', () => { const state = { settings: {}, preferences: {} }; const setInserterOpened = () => {}; expect( getSettings( state, setInserterOpened ) ).toEqual( { + outlineMode: true, focusMode: false, hasFixedToolbar: false, __experimentalSetIsInserterOpened: setInserterOpened, @@ -101,6 +102,7 @@ describe( 'selectors', () => { }; const setInserterOpened = () => {}; expect( getSettings( state, setInserterOpened ) ).toEqual( { + outlineMode: true, key: 'value', focusMode: true, hasFixedToolbar: true, diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss index 8350c69c9942df..9e4dce8b54aa23 100644 --- a/packages/edit-site/src/style.scss +++ b/packages/edit-site/src/style.scss @@ -3,9 +3,7 @@ @import "./components/block-editor/style.scss"; @import "./components/header/style.scss"; @import "./components/header/document-actions/style.scss"; -@import "./components/header/fullscreen-mode-close/style.scss"; @import "./components/header/more-menu/style.scss"; -@import "./components/left-sidebar/inserter-panel/style.scss"; @import "./components/navigation-sidebar/navigation-toggle/style.scss"; @import "./components/navigation-sidebar/navigation-panel/style.scss"; @import "./components/notices/style.scss"; @@ -46,10 +44,6 @@ body.toplevel_page_gutenberg-edit-site { min-height: calc(100vh - #{$admin-bar-height}); } - > .components-navigate-regions { - height: 100%; - } - // Todo: Remove this rule when edit site gets support // for opening unpinned sidebar items. .interface-complementary-area__pin-unpin-item.components-button { @@ -78,3 +72,4 @@ body.toplevel_page_gutenberg-edit-site { } @include wordpress-admin-schemes(); +@include default-block-widths(); diff --git a/packages/edit-site/src/utils/get-closest-available-template.js b/packages/edit-site/src/utils/get-closest-available-template.js new file mode 100644 index 00000000000000..e73be0204d753a --- /dev/null +++ b/packages/edit-site/src/utils/get-closest-available-template.js @@ -0,0 +1,31 @@ +/** + * External dependencies + */ +import { find } from 'lodash'; + +export default function getClosestAvailableTemplate( slug, templates ) { + const template = find( templates, { slug } ); + if ( template ) { + return template; + } + + switch ( slug ) { + case 'single': + case 'page': + return getClosestAvailableTemplate( 'singular', templates ); + case 'author': + case 'category': + case 'taxonomy': + case 'date': + case 'tag': + return getClosestAvailableTemplate( 'archive', templates ); + case 'front-page': + return getClosestAvailableTemplate( 'home', templates ); + case 'attachment': + return getClosestAvailableTemplate( 'single', templates ); + case 'privacy-policy': + return getClosestAvailableTemplate( 'page', templates ); + } + + return find( templates, { slug: 'index' } ); +} diff --git a/packages/edit-site/src/utils/get-template-info/constants.js b/packages/edit-site/src/utils/get-template-info/constants.js deleted file mode 100644 index c6facdbce79fea..00000000000000 --- a/packages/edit-site/src/utils/get-template-info/constants.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * WordPress dependencies - */ -import { __, _x } from '@wordpress/i18n'; - -export const TEMPLATES_DEFAULT_DETAILS = { - // General - 'front-page': { - title: _x( 'Front Page', 'template name' ), - description: __( - 'Front page template, whether it displays the blog posts index or a static page' - ), - }, - archive: { - title: _x( 'Archive', 'template name' ), - description: __( 'Generic archive template' ), - }, - singular: { - title: _x( 'Singular', 'template name' ), - description: __( 'Default template for both single posts and pages' ), - }, - index: { - title: _x( 'Index', 'template name' ), - description: __( 'Default template' ), - }, - search: { - title: _x( 'Search Results', 'template name' ), - description: __( 'Search results template' ), - }, - '404': { - title: _x( '404 (Not Found)', 'template name' ), - description: __( 'Template for "not found" errors' ), - }, - - // Pages - page: { - title: __( 'Single Page' ), - description: __( 'Template for single pages' ), - }, - - // Posts - home: { - title: __( 'Home Page' ), - description: __( 'Template for the latest blog posts' ), - }, - single: { - title: __( 'Single Post' ), - description: __( 'Template for single posts' ), - }, -}; diff --git a/packages/edit-site/src/utils/get-template-info/index.js b/packages/edit-site/src/utils/get-template-info/index.js deleted file mode 100644 index 71b7bd602931e5..00000000000000 --- a/packages/edit-site/src/utils/get-template-info/index.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Internal dependencies - */ -import { TEMPLATES_DEFAULT_DETAILS } from './constants'; - -/** - * Given a template entity, return information about it which is ready to be - * rendered, such as the title and description. - * - * @param {Object} template The template for which we need information. - * @return {Object} Information about the template, including title and description. - */ -export default function getTemplateInfo( template ) { - if ( ! template ) { - return {}; - } - const { title: defaultTitle, description: defaultDescription } = - TEMPLATES_DEFAULT_DETAILS[ template.slug ] ?? {}; - - let title = template?.title?.rendered ?? template.slug; - if ( title !== template.slug ) { - title = template.title.rendered; - } else if ( defaultTitle ) { - title = defaultTitle; - } - - const description = template?.excerpt?.rendered || defaultDescription; - return { title, description }; -} diff --git a/packages/edit-site/src/utils/get-template-info/test/__snapshots__/index.js.snap b/packages/edit-site/src/utils/get-template-info/test/__snapshots__/index.js.snap deleted file mode 100644 index 10003587248ca2..00000000000000 --- a/packages/edit-site/src/utils/get-template-info/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,22 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`get template info should return both a title and a description 1`] = ` -Object { - "description": "test description", - "title": "Index", -} -`; - -exports[`get template info should return both a title and a description 2`] = ` -Object { - "description": "Default template", - "title": "Index", -} -`; - -exports[`get template info should return both a title and a description 3`] = ` -Object { - "description": undefined, - "title": undefined, -} -`; diff --git a/packages/edit-site/src/utils/get-template-info/test/index.js b/packages/edit-site/src/utils/get-template-info/test/index.js deleted file mode 100644 index dbf0e9450d5574..00000000000000 --- a/packages/edit-site/src/utils/get-template-info/test/index.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Internal dependencies - */ -import getTemplateInfo from '../'; - -describe( 'get template info', () => { - it( 'should return an empty object if no template is passed', () => { - expect( getTemplateInfo( null ) ).toEqual( {} ); - expect( getTemplateInfo( undefined ) ).toEqual( {} ); - expect( getTemplateInfo( false ) ).toEqual( {} ); - } ); - - it( 'should return the default title if none is defined on the template', () => { - expect( getTemplateInfo( { slug: 'index' } ).title ).toEqual( 'Index' ); - } ); - - it( 'should return the rendered title if one is defined on the template', () => { - expect( - getTemplateInfo( { - slug: 'index', - title: { rendered: 'test title' }, - } ).title - ).toEqual( 'test title' ); - } ); - - it( 'should return the slug if no title is found', () => { - expect( - getTemplateInfo( { - slug: 'not a real template', - } ).title - ).toEqual( 'not a real template' ); - } ); - - it( 'should return the default description if none is defined on the template', () => { - expect( - getTemplateInfo( { - slug: 'index', - } ).description - ).toEqual( 'Default template' ); - } ); - - it( 'should return the rendered excerpt as description if defined on the template', () => { - expect( - getTemplateInfo( { - slug: 'index', - excerpt: { - rendered: 'test description', - }, - } ).description - ).toEqual( 'test description' ); - } ); - - it( 'should return both a title and a description', () => { - expect( - getTemplateInfo( { - slug: 'index', - excerpt: { - rendered: 'test description', - }, - } ) - ).toMatchSnapshot(); - - expect( - getTemplateInfo( { - slug: 'index', - } ) - ).toMatchSnapshot(); - - expect( getTemplateInfo( {} ) ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/edit-site/src/utils/index.js b/packages/edit-site/src/utils/index.js index 81512be7171961..1fc2fc98df0855 100644 --- a/packages/edit-site/src/utils/index.js +++ b/packages/edit-site/src/utils/index.js @@ -1,2 +1 @@ export { default as findTemplate } from './find-template'; -export { default as getTemplateInfo } from './get-template-info'; diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json index 3987e4752414c3..c143d0375db4ca 100644 --- a/packages/edit-widgets/package.json +++ b/packages/edit-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-widgets", - "version": "1.1.4", + "version": "1.1.5", "description": "Widgets Page module for WordPress..", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -22,12 +22,8 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", - "sideEffects": [ - "build-style/**", - "!((src|build|build-module)/components/**)" - ], "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/block-editor": "file:../block-editor", "@wordpress/block-library": "file:../block-library", @@ -54,7 +50,7 @@ "lodash": "^4.17.19", "reakit": "^1.1.0", "rememo": "^3.0.0", - "uuid": "^8.3.1" + "uuid": "^8.3.0" }, "publishConfig": { "access": "public" diff --git a/packages/edit-widgets/src/blocks/legacy-widget/block.json b/packages/edit-widgets/src/blocks/legacy-widget/block.json index 49b8c20eacd123..934ea46ca1493d 100644 --- a/packages/edit-widgets/src/blocks/legacy-widget/block.json +++ b/packages/edit-widgets/src/blocks/legacy-widget/block.json @@ -28,5 +28,6 @@ }, "parent": [ "core/widget-area" - ] + ], + "editorStyle": "wp-block-legacy-widget-editor" } diff --git a/packages/edit-widgets/src/blocks/widget-area/block.json b/packages/edit-widgets/src/blocks/widget-area/block.json index e5d4d713a3bd9e..fdec21c57e9311 100644 --- a/packages/edit-widgets/src/blocks/widget-area/block.json +++ b/packages/edit-widgets/src/blocks/widget-area/block.json @@ -15,5 +15,7 @@ "customClassName": false, "reusable": false, "__experimentalToolbar": false - } + }, + "editorStyle": "wp-block-widget-area-editor", + "style": "wp-block-widget-area" } diff --git a/packages/edit-widgets/src/blocks/widget-area/edit/index.js b/packages/edit-widgets/src/blocks/widget-area/edit/index.js index 08fa1485fc3f5f..e898920fa529ac 100644 --- a/packages/edit-widgets/src/blocks/widget-area/edit/index.js +++ b/packages/edit-widgets/src/blocks/widget-area/edit/index.js @@ -16,6 +16,8 @@ import { Panel, PanelBody } from '@wordpress/components'; */ import WidgetAreaInnerBlocks from './inner-blocks'; +/** @typedef {import('@wordpress/element').RefObject} RefObject */ + export default function WidgetAreaEdit( { clientId, className, @@ -33,7 +35,7 @@ export default function WidgetAreaEdit( { ( openState ) => setIsWidgetAreaOpen( clientId, openState ), [ clientId ] ); - const isDragging = useIsDragging(); + const isDragging = useIsDragging( wrapper ); const isDraggingWithin = useIsDraggingWithin( wrapper ); const [ openedWhileDragging, setOpenedWhileDragging ] = useState( false ); @@ -82,14 +84,16 @@ export default function WidgetAreaEdit( { /** * A React hook to determine if dragging is active. * - * @typedef {import('@wordpress/element').RefObject} RefObject + * @param {RefObject<HTMLElement>} elementRef The target elementRef object. * * @return {boolean} Is dragging within the entire document. */ -const useIsDragging = () => { +const useIsDragging = ( elementRef ) => { const [ isDragging, setIsDragging ] = useState( false ); useEffect( () => { + const { ownerDocument } = elementRef.current; + function handleDragStart() { setIsDragging( true ); } @@ -98,12 +102,12 @@ const useIsDragging = () => { setIsDragging( false ); } - document.addEventListener( 'dragstart', handleDragStart ); - document.addEventListener( 'dragend', handleDragEnd ); + ownerDocument.addEventListener( 'dragstart', handleDragStart ); + ownerDocument.addEventListener( 'dragend', handleDragEnd ); return () => { - document.removeEventListener( 'dragstart', handleDragStart ); - document.removeEventListener( 'dragend', handleDragEnd ); + ownerDocument.removeEventListener( 'dragstart', handleDragStart ); + ownerDocument.removeEventListener( 'dragend', handleDragEnd ); }; }, [] ); @@ -113,8 +117,6 @@ const useIsDragging = () => { /** * A React hook to determine if it's dragging within the target element. * - * @typedef {import('@wordpress/element').RefObject} RefObject - * * @param {RefObject<HTMLElement>} elementRef The target elementRef object. * * @return {boolean} Is dragging within the target element. @@ -123,6 +125,8 @@ const useIsDraggingWithin = ( elementRef ) => { const [ isDraggingWithin, setIsDraggingWithin ] = useState( false ); useEffect( () => { + const { ownerDocument } = elementRef.current; + function handleDragStart( event ) { // Check the first time when the dragging starts. handleDragEnter( event ); @@ -144,14 +148,14 @@ const useIsDraggingWithin = ( elementRef ) => { // Bind these events to the document to catch all drag events. // Ideally, we can also use `event.relatedTarget`, but sadly that doesn't work in Safari. - document.addEventListener( 'dragstart', handleDragStart ); - document.addEventListener( 'dragend', handleDragEnd ); - document.addEventListener( 'dragenter', handleDragEnter ); + ownerDocument.addEventListener( 'dragstart', handleDragStart ); + ownerDocument.addEventListener( 'dragend', handleDragEnd ); + ownerDocument.addEventListener( 'dragenter', handleDragEnter ); return () => { - document.removeEventListener( 'dragstart', handleDragStart ); - document.removeEventListener( 'dragend', handleDragEnd ); - document.removeEventListener( 'dragenter', handleDragEnter ); + ownerDocument.removeEventListener( 'dragstart', handleDragStart ); + ownerDocument.removeEventListener( 'dragend', handleDragEnd ); + ownerDocument.removeEventListener( 'dragenter', handleDragEnter ); }; }, [] ); diff --git a/packages/edit-widgets/src/components/keyboard-shortcuts/index.js b/packages/edit-widgets/src/components/keyboard-shortcuts/index.js index 6494d83cafc27d..3b0e00e5a155e3 100644 --- a/packages/edit-widgets/src/components/keyboard-shortcuts/index.js +++ b/packages/edit-widgets/src/components/keyboard-shortcuts/index.js @@ -2,7 +2,10 @@ * WordPress dependencies */ import { useEffect } from '@wordpress/element'; -import { useShortcut } from '@wordpress/keyboard-shortcuts'; +import { + useShortcut, + store as keyboardShortcutsStore, +} from '@wordpress/keyboard-shortcuts'; import { useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; @@ -42,7 +45,7 @@ function KeyboardShortcuts() { function KeyboardShortcutsRegister() { // Registering the shortcuts - const { registerShortcut } = useDispatch( 'core/keyboard-shortcuts' ); + const { registerShortcut } = useDispatch( keyboardShortcutsStore ); useEffect( () => { registerShortcut( { name: 'core/edit-widgets/undo', diff --git a/packages/edit-widgets/src/components/layout/interface.js b/packages/edit-widgets/src/components/layout/interface.js index c0a781b38a421b..db6440afbd6291 100644 --- a/packages/edit-widgets/src/components/layout/interface.js +++ b/packages/edit-widgets/src/components/layout/interface.js @@ -2,10 +2,16 @@ * WordPress dependencies */ import { Button } from '@wordpress/components'; -import { useViewportMatch } from '@wordpress/compose'; +import { + __experimentalUseDialog as useDialog, + useViewportMatch, +} from '@wordpress/compose'; import { close } from '@wordpress/icons'; -import { __experimentalLibrary as Library } from '@wordpress/block-editor'; -import { useEffect } from '@wordpress/element'; +import { + __experimentalLibrary as Library, + __unstableUseEditorStyles as useEditorStyles, +} from '@wordpress/block-editor'; +import { useEffect, useRef } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { InterfaceSkeleton, ComplementaryArea } from '@wordpress/interface'; import { __ } from '@wordpress/i18n'; @@ -15,7 +21,6 @@ import { __ } from '@wordpress/i18n'; */ import Header from '../header'; import WidgetAreasBlockEditorContent from '../widget-areas-block-editor-content'; -import PopoverWrapper from './popover-wrapper'; import useWidgetLibraryInsertionPoint from '../../hooks/use-widget-library-insertion-point'; const interfaceLabels = { @@ -41,6 +46,9 @@ function Interface( { blockEditorSettings } ) { ).getActiveComplementaryArea( 'core/edit-widgets' ), isInserterOpened: !! select( 'core/edit-widgets' ).isInserterOpened(), } ) ); + const ref = useRef(); + + useEditorStyles( ref, blockEditorSettings.styles ); // Inserter and Sidebars are mutually exclusive useEffect( () => { @@ -55,41 +63,41 @@ function Interface( { blockEditorSettings } ) { } }, [ isInserterOpened, isHugeViewport ] ); + const [ inserterDialogRef, inserterDialogProps ] = useDialog( { + onClose: () => setIsInserterOpened( false ), + } ); + return ( <InterfaceSkeleton + ref={ ref } labels={ interfaceLabels } header={ <Header /> } - leftSidebar={ + secondarySidebar={ isInserterOpened && ( - <PopoverWrapper - className="edit-widgets-layout__inserter-panel-popover-wrapper" - onClose={ () => setIsInserterOpened( false ) } + <div + ref={ inserterDialogRef } + { ...inserterDialogProps } + className="edit-widgets-layout__inserter-panel" > - <div className="edit-widgets-layout__inserter-panel"> - <div className="edit-widgets-layout__inserter-panel-header"> - <Button - icon={ close } - onClick={ () => - setIsInserterOpened( false ) - } - /> - </div> - <div className="edit-widgets-layout__inserter-panel-content"> - <Library - showInserterHelpPanel - onSelect={ () => { - if ( isMobileViewport ) { - setIsInserterOpened( false ); - } - } } - rootClientId={ rootClientId } - __experimentalInsertionIndex={ - insertionIndex + <div className="edit-widgets-layout__inserter-panel-header"> + <Button + icon={ close } + onClick={ () => setIsInserterOpened( false ) } + /> + </div> + <div className="edit-widgets-layout__inserter-panel-content"> + <Library + showInserterHelpPanel + onSelect={ () => { + if ( isMobileViewport ) { + setIsInserterOpened( false ); } - /> - </div> + } } + rootClientId={ rootClientId } + __experimentalInsertionIndex={ insertionIndex } + /> </div> - </PopoverWrapper> + </div> ) } sidebar={ diff --git a/packages/edit-widgets/src/components/layout/popover-wrapper.js b/packages/edit-widgets/src/components/layout/popover-wrapper.js deleted file mode 100644 index 375ba0ace1179d..00000000000000 --- a/packages/edit-widgets/src/components/layout/popover-wrapper.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * WordPress dependencies - */ -import { - withConstrainedTabbing, - withFocusReturn, - withFocusOutside, -} from '@wordpress/components'; -import { Component } from '@wordpress/element'; -import { ESCAPE } from '@wordpress/keycodes'; - -function stopPropagation( event ) { - event.stopPropagation(); -} - -const DetectOutside = withFocusOutside( - class extends Component { - handleFocusOutside( event ) { - this.props.onFocusOutside( event ); - } - - render() { - return this.props.children; - } - } -); - -const FocusManaged = withConstrainedTabbing( - withFocusReturn( ( { children } ) => children ) -); - -export default function PopoverWrapper( { onClose, children, className } ) { - // Event handlers - const maybeClose = ( event ) => { - // Close on escape - if ( event.keyCode === ESCAPE && onClose ) { - event.stopPropagation(); - onClose(); - } - }; - - // Disable reason: this stops certain events from propagating outside of the component. - // - onMouseDown is disabled as this can cause interactions with other DOM elements - /* eslint-disable jsx-a11y/no-static-element-interactions */ - return ( - <div - className={ className } - onKeyDown={ maybeClose } - onMouseDown={ stopPropagation } - > - <DetectOutside onFocusOutside={ onClose }> - <FocusManaged>{ children }</FocusManaged> - </DetectOutside> - </div> - ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ -} diff --git a/packages/edit-widgets/src/components/layout/style.scss b/packages/edit-widgets/src/components/layout/style.scss index 4b7c34cf9451d3..b42fc1a39aad42 100644 --- a/packages/edit-widgets/src/components/layout/style.scss +++ b/packages/edit-widgets/src/components/layout/style.scss @@ -1,15 +1,3 @@ - -// Ideally we don't need all these nested divs. -// Removing these requires a refactoring of the different a11y HoCs. -.edit-widgets-layout__inserter-panel-popover-wrapper { - &, - & > div, - & > div > div, - & > div > div > div { - height: 100%; - } -} - .edit-widgets-layout__inserter-panel { height: 100%; display: flex; diff --git a/packages/edit-widgets/src/components/notices/index.js b/packages/edit-widgets/src/components/notices/index.js index 6e45a3e8005715..0e15bbf8a15bf1 100644 --- a/packages/edit-widgets/src/components/notices/index.js +++ b/packages/edit-widgets/src/components/notices/index.js @@ -8,17 +8,18 @@ import { filter } from 'lodash'; */ import { SnackbarList } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; function Notices() { const { notices } = useSelect( ( select ) => { return { - notices: select( 'core/notices' ).getNotices(), + notices: select( noticesStore ).getNotices(), }; }, [] ); const snackbarNotices = filter( notices, { type: 'snackbar', } ); - const { removeNotice } = useDispatch( 'core/notices' ); + const { removeNotice } = useDispatch( noticesStore ); return ( <SnackbarList diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-content/style.scss b/packages/edit-widgets/src/components/widget-areas-block-editor-content/style.scss index 2b1bf51d97db74..ad27a373c9fb52 100644 --- a/packages/edit-widgets/src/components/widget-areas-block-editor-content/style.scss +++ b/packages/edit-widgets/src/components/widget-areas-block-editor-content/style.scss @@ -1,3 +1,16 @@ .edit-widgets-block-editor { position: relative; + + // The button element easily inherits styles that are meant for the editor style. + // These rules enhance the specificity to reduce that inheritance. + // This is copied from edit-post/src/components/style.scss but without the padding. + & .components-button { + font-family: $default-font; + font-size: $default-font-size; + + &.is-tertiary, + &.has-icon { + padding: 6px; + } + } } diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js index c29107a394093c..27545b176bf43b 100644 --- a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js +++ b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js @@ -6,18 +6,13 @@ import { defaultTo } from 'lodash'; /** * WordPress dependencies */ -import { - DropZoneProvider, - SlotFillProvider, - FocusReturnProvider, -} from '@wordpress/components'; +import { DropZoneProvider, SlotFillProvider } from '@wordpress/components'; import { uploadMedia } from '@wordpress/media-utils'; import { useDispatch, useSelect } from '@wordpress/data'; import { useMemo } from '@wordpress/element'; import { BlockEditorProvider, BlockEditorKeyboardShortcuts, - __unstableEditorStyles as EditorStyles, } from '@wordpress/block-editor'; import { ReusableBlocksMenuItems } from '@wordpress/reusable-blocks'; @@ -46,7 +41,8 @@ export default function WidgetAreasBlockEditorProvider( { 'postType', 'wp_block' ), - } ) + } ), + [] ); const { setIsInserterOpened } = useDispatch( 'core/edit-widgets' ); @@ -85,26 +81,23 @@ export default function WidgetAreasBlockEditorProvider( { return ( <> - <EditorStyles styles={ settings.styles } /> <BlockEditorKeyboardShortcuts.Register /> <KeyboardShortcuts.Register /> <SlotFillProvider> <DropZoneProvider> - <FocusReturnProvider> - <BlockEditorProvider - value={ blocks } - onInput={ onInput } - onChange={ onChange } - settings={ settings } - useSubRegistry={ false } - { ...props } - > - { children } - <ReusableBlocksMenuItems - rootClientId={ widgetAreaId } - /> - </BlockEditorProvider> - </FocusReturnProvider> + <BlockEditorProvider + value={ blocks } + onInput={ onInput } + onChange={ onChange } + settings={ settings } + useSubRegistry={ false } + { ...props } + > + { children } + <ReusableBlocksMenuItems + rootClientId={ widgetAreaId } + /> + </BlockEditorProvider> </DropZoneProvider> </SlotFillProvider> </> diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index 6823343f6174fa..e2d3a4813fd94e 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -5,14 +5,12 @@ import { registerBlockType, unstable__bootstrapServerSideBlockDefinitions, // eslint-disable-line camelcase } from '@wordpress/blocks'; -import '@wordpress/notices'; import { render } from '@wordpress/element'; import { registerCoreBlocks, __experimentalGetCoreBlocks, __experimentalRegisterExperimentalCoreBlocks, } from '@wordpress/block-library'; -import '@wordpress/reusable-blocks'; /** * Internal dependencies @@ -36,7 +34,7 @@ export function initialize( id, settings ) { registerCoreBlocks( coreBlocks ); if ( process.env.GUTENBERG_PHASE === 2 ) { - __experimentalRegisterExperimentalCoreBlocks( settings ); + __experimentalRegisterExperimentalCoreBlocks(); } registerBlock( createLegacyWidget( settings ) ); registerBlock( widgetArea ); diff --git a/packages/edit-widgets/src/store/actions.js b/packages/edit-widgets/src/store/actions.js index 3b0614dce94df8..feebe301bf5091 100644 --- a/packages/edit-widgets/src/store/actions.js +++ b/packages/edit-widgets/src/store/actions.js @@ -8,6 +8,7 @@ import { invert } from 'lodash'; */ import { __, sprintf } from '@wordpress/i18n'; import { dispatch as dataDispatch } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies @@ -57,7 +58,7 @@ export function* saveEditedWidgetAreas() { try { yield* saveWidgetAreas( editedWidgetAreas ); yield dispatch( - 'core/notices', + noticesStore, 'createSuccessNotice', __( 'Widgets saved.' ), { @@ -66,7 +67,7 @@ export function* saveEditedWidgetAreas() { ); } catch ( e ) { yield dispatch( - 'core/notices', + noticesStore, 'createErrorNotice', /* translators: %s: The error message. */ sprintf( __( 'There was an error. %s' ), e.message ), diff --git a/packages/edit-widgets/src/store/batch-processing/constants.js b/packages/edit-widgets/src/store/batch-processing/constants.js index 0ad0691c43a463..56c0bbe4fb1e89 100644 --- a/packages/edit-widgets/src/store/batch-processing/constants.js +++ b/packages/edit-widgets/src/store/batch-processing/constants.js @@ -1,7 +1,7 @@ /** * Module Constants */ -export const MODULE_KEY = 'core/__experimental-batch-processing'; +export const STORE_NAME = 'core/__experimental-batch-processing'; export const BATCH_MAX_SIZE = 20; diff --git a/packages/edit-widgets/src/store/batch-processing/controls.js b/packages/edit-widgets/src/store/batch-processing/controls.js index 431fc1a49bf941..a9314a5ecaf700 100644 --- a/packages/edit-widgets/src/store/batch-processing/controls.js +++ b/packages/edit-widgets/src/store/batch-processing/controls.js @@ -6,7 +6,7 @@ import { createRegistryControl } from '@wordpress/data'; /** * Internal dependencies */ -import { MODULE_KEY, STATE_ERROR } from './constants'; +import { STORE_NAME, STATE_ERROR } from './constants'; /** * Calls a selector using chosen registry. @@ -58,25 +58,25 @@ export function enqueueItemAndAutocommit( queue, context, item ) { const controls = { SELECT: createRegistryControl( ( registry ) => ( { selectorName, args } ) => { - return registry.select( MODULE_KEY )[ selectorName ]( ...args ); + return registry.select( STORE_NAME )[ selectorName ]( ...args ); } ), DISPATCH: createRegistryControl( ( registry ) => ( { actionName, args } ) => { - return registry.dispatch( MODULE_KEY )[ actionName ]( ...args ); + return registry.dispatch( STORE_NAME )[ actionName ]( ...args ); } ), ENQUEUE_ITEM_AND_AUTOCOMMIT: createRegistryControl( ( registry ) => async ( { queue, context, item } ) => { const { itemId } = await registry - .dispatch( MODULE_KEY ) + .dispatch( STORE_NAME ) .enqueueItem( queue, context, item ); // @TODO autocommit when batch size exceeds the maximum or n milliseconds passes const batch = await registry - .dispatch( MODULE_KEY ) + .dispatch( STORE_NAME ) .processBatch( queue, context ); if ( batch.state === STATE_ERROR ) { @@ -92,12 +92,12 @@ const controls = { const { transactions, queue } = batch; const transaction = transactions[ transactionId ]; const processor = registry - .select( MODULE_KEY ) + .select( STORE_NAME ) .getProcessor( queue ); if ( ! processor ) { throw new Error( `There is no batch processor registered for "${ queue }" queue. ` + - `Register one by dispatching registerProcessor() action on ${ MODULE_KEY } store.` + `Register one by dispatching registerProcessor() action on ${ STORE_NAME } store.` ); } const itemIds = transaction.items.map( ( { id } ) => id ); diff --git a/packages/edit-widgets/src/store/batch-processing/index.js b/packages/edit-widgets/src/store/batch-processing/index.js index c2319d7b58a688..c50ae697ed9985 100644 --- a/packages/edit-widgets/src/store/batch-processing/index.js +++ b/packages/edit-widgets/src/store/batch-processing/index.js @@ -10,7 +10,7 @@ import * as actions from './actions'; import reducer from './reducer'; import controls from './controls'; import * as selectors from './selectors'; -import { MODULE_KEY } from './constants'; +import { STORE_NAME } from './constants'; /** * Block editor data store configuration. @@ -19,13 +19,13 @@ import { MODULE_KEY } from './constants'; * * @type {Object} */ -export const storeConfig = { +const storeConfig = { actions, reducer, controls, selectors, }; -const store = registerStore( MODULE_KEY, storeConfig ); +const store = registerStore( STORE_NAME, storeConfig ); export default store; diff --git a/packages/edit-widgets/src/store/index.js b/packages/edit-widgets/src/store/index.js index 06b847a9d2575a..bdbf487125e33f 100644 --- a/packages/edit-widgets/src/store/index.js +++ b/packages/edit-widgets/src/store/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import apiFetch from '@wordpress/api-fetch'; -import { registerStore } from '@wordpress/data'; +import { createReduxStore, register } from '@wordpress/data'; /** * Internal dependencies @@ -17,7 +17,7 @@ import './batch-support'; /** * Module Constants */ -const MODULE_KEY = 'core/edit-widgets'; +const STORE_NAME = 'core/edit-widgets'; /** * Block editor data store configuration. @@ -26,7 +26,7 @@ const MODULE_KEY = 'core/edit-widgets'; * * @type {Object} */ -export const storeConfig = { +const storeConfig = { reducer, controls, selectors, @@ -34,7 +34,16 @@ export const storeConfig = { actions, }; -const store = registerStore( MODULE_KEY, storeConfig ); +/** + * Store definition for the edit widgets namespace. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore + * + * @type {Object} + */ +export const store = createReduxStore( STORE_NAME, storeConfig ); + +register( store ); // This package uses a few in-memory post types as wrappers for convenience. // This middleware prevents any network requests related to these types as they are @@ -46,5 +55,3 @@ apiFetch.use( function ( options, next ) { return next( options ); } ); - -export default store; diff --git a/packages/edit-widgets/src/style.scss b/packages/edit-widgets/src/style.scss index f25f28fd650ab2..3e3e7ed2e04a76 100644 --- a/packages/edit-widgets/src/style.scss +++ b/packages/edit-widgets/src/style.scss @@ -43,10 +43,6 @@ body.widgets-php { min-height: calc(100vh - #{ $admin-bar-height }); } - > .components-navigate-regions { - height: 100%; - } - .interface-interface-skeleton__content { background-color: #f1f1f1; } diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index 9e679e68746018..edfc30a3744fcb 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -2,191 +2,196 @@ ## Unreleased +### New Feature + +- Added a store definition `store` for the editor namespace to use with `@wordpress/data` API ([#26655](https://github.com/WordPress/gutenberg/pull/26655)). + ## 9.21.0 (2020-09-03) ### Enhancement -- The `UnsavedChangesWarning` component is now using `__experimentalGetDirtyEntityRecords` to determine if there were changes. +- The `UnsavedChangesWarning` component is now using `__experimentalGetDirtyEntityRecords` to determine if there were changes. ## 9.4.0 (2019-06-12) ### Deprecations -- The following components are deprecated as moved to the `@wordpress/block-editor` package: - - Autocomplete, - - AlignmentToolbar, - - BlockAlignmentToolbar, - - BlockControls, - - BlockEdit, - - BlockEditorKeyboardShortcuts, - - BlockFormatControls, - - BlockIcon, - - BlockInspector, - - BlockList, - - BlockMover, - - BlockNavigationDropdown, - - BlockSelectionClearer, - - BlockSettingsMenu, - - BlockTitle, - - BlockToolbar, - - ColorPalette, - - ContrastChecker, - - CopyHandler, - - createCustomColorsHOC, - - DefaultBlockAppender, - - FontSizePicker, - - getColorClassName, - - getColorObjectByAttributeValues, - - getColorObjectByColorValue, - - getFontSize, - - getFontSizeClass, - - Inserter, - - InnerBlocks, - - InspectorAdvancedControls, - - InspectorControls, - - PanelColorSettings, - - PlainText, - - RichText, - - RichTextShortcut, - - RichTextToolbarButton, - - RichTextInserterItem, - - MediaPlaceholder, - - MediaUpload, - - MediaUploadCheck, - - MultiBlocksSwitcher, - - MultiSelectScrollIntoView, - - NavigableToolbar, - - ObserveTyping, - - PreserveScrollInReorder, - - SkipToSelectedBlock, - - URLInput, - - URLInputButton, - - URLPopover, - - Warning, - - WritingFlow, - - withColorContext, - - withColors, - - withFontSizes. -- The following actions are deprecated as moved to the `core/block-editor` store: - - resetBlocks, - - receiveBlocks, - - updateBlock, - - updateBlockAttributes, - - selectBlock, - - startMultiSelect, - - stopMultiSelect, - - multiSelect, - - clearSelectedBlock, - - toggleSelection, - - replaceBlocks, - - replaceBlock, - - moveBlocksDown, - - moveBlocksUp, - - moveBlockToPosition, - - insertBlock, - - insertBlocks, - - showInsertionPoint, - - hideInsertionPoint, - - setTemplateValidity, - - synchronizeTemplate, - - mergeBlocks, - - removeBlocks, - - removeBlock, - - toggleBlockMode, - - startTyping, - - stopTyping, - - enterFormattedText, - - exitFormattedText, - - insertDefaultBlock, - - updateBlockListSettings. -- The following selectors are deprecated as moved to the `core/block-editor` store: - - getBlockDependantsCacheBust, - - getBlockName, - - isBlockValid, - - getBlockAttributes, - - getBlock, - - getBlocks, - - getClientIdsOfDescendants, - - getClientIdsWithDescendants, - - getGlobalBlockCount, - - getBlocksByClientId, - - getBlockCount, - - getBlockSelectionStart, - - getBlockSelectionEnd, - - getSelectedBlockCount, - - hasSelectedBlock, - - getSelectedBlockClientId, - - getSelectedBlock, - - getBlockRootClientId, - - getBlockHierarchyRootClientId, - - getAdjacentBlockClientId, - - getPreviousBlockClientId, - - getNextBlockClientId, - - getSelectedBlocksInitialCaretPosition, - - getMultiSelectedBlockClientIds, - - getMultiSelectedBlocks, - - getFirstMultiSelectedBlockClientId, - - getLastMultiSelectedBlockClientId, - - isFirstMultiSelectedBlock, - - isBlockMultiSelected, - - isAncestorMultiSelected, - - getMultiSelectedBlocksStartClientId, - - getMultiSelectedBlocksEndClientId, - - getBlockOrder, - - getBlockIndex, - - isBlockSelected, - - hasSelectedInnerBlock, - - isBlockWithinSelection, - - hasMultiSelection, - - isMultiSelecting, - - isSelectionEnabled, - - getBlockMode =, - - isTyping, - - isCaretWithinFormattedText, - - getBlockInsertionPoint, - - isBlockInsertionPointVisible, - - isValidTemplate, - - getTemplate, - - getTemplateLock, - - canInsertBlockType, - - getInserterItems, - - hasInserterItems, - - getBlockListSettings. +- The following components are deprecated as moved to the `@wordpress/block-editor` package: + - Autocomplete, + - AlignmentToolbar, + - BlockAlignmentToolbar, + - BlockControls, + - BlockEdit, + - BlockEditorKeyboardShortcuts, + - BlockFormatControls, + - BlockIcon, + - BlockInspector, + - BlockList, + - BlockMover, + - BlockNavigationDropdown, + - BlockSelectionClearer, + - BlockSettingsMenu, + - BlockTitle, + - BlockToolbar, + - ColorPalette, + - ContrastChecker, + - CopyHandler, + - createCustomColorsHOC, + - DefaultBlockAppender, + - FontSizePicker, + - getColorClassName, + - getColorObjectByAttributeValues, + - getColorObjectByColorValue, + - getFontSize, + - getFontSizeClass, + - Inserter, + - InnerBlocks, + - InspectorAdvancedControls, + - InspectorControls, + - PanelColorSettings, + - PlainText, + - RichText, + - RichTextShortcut, + - RichTextToolbarButton, + - RichTextInserterItem, + - MediaPlaceholder, + - MediaUpload, + - MediaUploadCheck, + - MultiBlocksSwitcher, + - MultiSelectScrollIntoView, + - NavigableToolbar, + - ObserveTyping, + - PreserveScrollInReorder, + - SkipToSelectedBlock, + - URLInput, + - URLInputButton, + - URLPopover, + - Warning, + - WritingFlow, + - withColorContext, + - withColors, + - withFontSizes. +- The following actions are deprecated as moved to the `core/block-editor` store: + - resetBlocks, + - receiveBlocks, + - updateBlock, + - updateBlockAttributes, + - selectBlock, + - startMultiSelect, + - stopMultiSelect, + - multiSelect, + - clearSelectedBlock, + - toggleSelection, + - replaceBlocks, + - replaceBlock, + - moveBlocksDown, + - moveBlocksUp, + - moveBlockToPosition, + - insertBlock, + - insertBlocks, + - showInsertionPoint, + - hideInsertionPoint, + - setTemplateValidity, + - synchronizeTemplate, + - mergeBlocks, + - removeBlocks, + - removeBlock, + - toggleBlockMode, + - startTyping, + - stopTyping, + - enterFormattedText, + - exitFormattedText, + - insertDefaultBlock, + - updateBlockListSettings. +- The following selectors are deprecated as moved to the `core/block-editor` store: + - getBlockDependantsCacheBust, + - getBlockName, + - isBlockValid, + - getBlockAttributes, + - getBlock, + - getBlocks, + - getClientIdsOfDescendants, + - getClientIdsWithDescendants, + - getGlobalBlockCount, + - getBlocksByClientId, + - getBlockCount, + - getBlockSelectionStart, + - getBlockSelectionEnd, + - getSelectedBlockCount, + - hasSelectedBlock, + - getSelectedBlockClientId, + - getSelectedBlock, + - getBlockRootClientId, + - getBlockHierarchyRootClientId, + - getAdjacentBlockClientId, + - getPreviousBlockClientId, + - getNextBlockClientId, + - getSelectedBlocksInitialCaretPosition, + - getMultiSelectedBlockClientIds, + - getMultiSelectedBlocks, + - getFirstMultiSelectedBlockClientId, + - getLastMultiSelectedBlockClientId, + - isFirstMultiSelectedBlock, + - isBlockMultiSelected, + - isAncestorMultiSelected, + - getMultiSelectedBlocksStartClientId, + - getMultiSelectedBlocksEndClientId, + - getBlockOrder, + - getBlockIndex, + - isBlockSelected, + - hasSelectedInnerBlock, + - isBlockWithinSelection, + - hasMultiSelection, + - isMultiSelecting, + - isSelectionEnabled, + - getBlockMode =, + - isTyping, + - isCaretWithinFormattedText, + - getBlockInsertionPoint, + - isBlockInsertionPointVisible, + - isValidTemplate, + - getTemplate, + - getTemplateLock, + - canInsertBlockType, + - getInserterItems, + - hasInserterItems, + - getBlockListSettings. ## 9.3.0 (2019-05-21) ### Deprecations -- The `getAutosave`, `getAutosaveAttribute`, and `hasAutosave` selectors are deprecated. Please use the `getAutosave` selector in the `@wordpress/core-data` package. -- The `resetAutosave` action is deprecated. An equivalent action `receiveAutosaves` has been added to the `@wordpress/core-data` package. -- `ServerSideRender` component was deprecated. The component is now available in `@wordpress/server-side-render`. + +- The `getAutosave`, `getAutosaveAttribute`, and `hasAutosave` selectors are deprecated. Please use the `getAutosave` selector in the `@wordpress/core-data` package. +- The `resetAutosave` action is deprecated. An equivalent action `receiveAutosaves` has been added to the `@wordpress/core-data` package. +- `ServerSideRender` component was deprecated. The component is now available in `@wordpress/server-side-render`. ### Internal -- Refactor setupEditor effects to action-generator using controls ([#14513](https://github.com/WordPress/gutenberg/pull/14513)) -- Remove redux-multi dependency (no longer needed/used with above refactor) -- Replace internal controls definitions with usage of new @wordpress/data-controls package (see [#15435](https://github.com/WordPress/gutenberg/pull/15435) +- Refactor setupEditor effects to action-generator using controls ([#14513](https://github.com/WordPress/gutenberg/pull/14513)) +- Remove redux-multi dependency (no longer needed/used with above refactor) +- Replace internal controls definitions with usage of new @wordpress/data-controls package (see [#15435](https://github.com/WordPress/gutenberg/pull/15435) ## 9.1.0 (2019-03-06) ### New Features -- Added `createCustomColorsHOC` for creating a higher order `withCustomColors` component. -- Added a new `TextEditorGlobalKeyboardShortcuts` component. +- Added `createCustomColorsHOC` for creating a higher order `withCustomColors` component. +- Added a new `TextEditorGlobalKeyboardShortcuts` component. ### Deprecations -- `EditorGlobalKeyboardShortcuts` has been deprecated in favor of `VisualEditorGlobalKeyboardShortcuts`. +- `EditorGlobalKeyboardShortcuts` has been deprecated in favor of `VisualEditorGlobalKeyboardShortcuts`. ### Bug Fixes -- BlockSwitcher will now consistently render an icon for block multi-selections. +- BlockSwitcher will now consistently render an icon for block multi-selections. ### Internal -- Removed `jQuery` dependency. -- Removed `TinyMCE` dependency. -- RichText: improve format boundaries. -- Refactor all post effects to action-generators using controls ([#13716](https://github.com/WordPress/gutenberg/pull/13716)) +- Removed `jQuery` dependency. +- Removed `TinyMCE` dependency. +- RichText: improve format boundaries. +- Refactor all post effects to action-generators using controls ([#13716](https://github.com/WordPress/gutenberg/pull/13716)) ## 9.0.7 (2019-01-03) @@ -194,14 +199,14 @@ ### Bug Fixes -- Restore the `block` prop in the `BlockListBlock` filter. +- Restore the `block` prop in the `BlockListBlock` filter. ## 9.0.5 (2018-12-12) ### Bug Fixes -- `getEditedPostAttribute` now correctly returns the merged result of edits as a partial change when given `'meta'` as the `attributeName`. -- Fixes an error and unrecoverable state which occurs on autosave completion for a `'publicly_queryable' => false` post type. +- `getEditedPostAttribute` now correctly returns the merged result of edits as a partial change when given `'meta'` as the `attributeName`. +- Fixes an error and unrecoverable state which occurs on autosave completion for a `'publicly_queryable' => false` post type. ## 9.0.4 (2018-11-30) @@ -215,105 +220,105 @@ ### Breaking Changes -- `PostPublishPanelToggle` has been removed. Use `PostPublishButton` instead. +- `PostPublishPanelToggle` has been removed. Use `PostPublishButton` instead. ## 8.0.0 (2018-11-15) ### Breaking Changes -- The reusable blocks actions and selectors have been marked as experimental. +- The reusable blocks actions and selectors have been marked as experimental. ### Bug Fixes -- Stop propagating to DOM elements the `focusOnMount` prop from `NavigableToolbar` components +- Stop propagating to DOM elements the `focusOnMount` prop from `NavigableToolbar` components ## 7.0.1 (2018-11-12) ### Polish -- Remove unnecessary `locale` prop usage [#11649](https://github.com/WordPress/gutenberg/pull/11649) +- Remove unnecessary `locale` prop usage [#11649](https://github.com/WordPress/gutenberg/pull/11649) ### Bug Fixes -- Fix multi-selection triggering too often when using floated blocks. +- Fix multi-selection triggering too often when using floated blocks. ## 7.0.0 (2018-11-12) ### Breaking Change -- The `PanelColor` component has been removed. +- The `PanelColor` component has been removed. ### New Feature -- In `NavigableToolbar`, a property focusOnMount was added, if true, the toolbar will get focus as soon as it mounted. Defaults to false. +- In `NavigableToolbar`, a property focusOnMount was added, if true, the toolbar will get focus as soon as it mounted. Defaults to false. ### Bug Fixes -- Avoid unnecessary re-renders when navigating between blocks. -- PostPublishPanel: return focus to element that opened the panel -- Capture focus on self in InsertionPoint inserter -- Correct insertion point opacity selector -- Set code editor as RTL +- Avoid unnecessary re-renders when navigating between blocks. +- PostPublishPanel: return focus to element that opened the panel +- Capture focus on self in InsertionPoint inserter +- Correct insertion point opacity selector +- Set code editor as RTL ## 6.2.1 (2018-11-09) ### Deprecations -- `PostPublishPanelToggle` has been deprecated in favor of `PostPublishButton`. +- `PostPublishPanelToggle` has been deprecated in favor of `PostPublishButton`. ### Polish -- Reactive block styles. +- Reactive block styles. ## 6.2.0 (2018-11-09) ### New Features -- Adjust a11y roles for menu items, and make sure screen readers can properly use BlockNavigationList ([#11431](https://github.com/WordPress/gutenberg/issues/11431)). +- Adjust a11y roles for menu items, and make sure screen readers can properly use BlockNavigationList ([#11431](https://github.com/WordPress/gutenberg/issues/11431)). ## 6.1.1 (2018-11-03) ### Polish -- Remove `findDOMNode` usage from the `Inserter` component. -- Remove `findDOMNode` usage from the `Block` component. -- Remove `findDOMNode` usage from the `NavigableToolbar` component. +- Remove `findDOMNode` usage from the `Inserter` component. +- Remove `findDOMNode` usage from the `Block` component. +- Remove `findDOMNode` usage from the `NavigableToolbar` component. ## 6.1.0 (2018-10-30) ### Deprecations -- The Reusable blocks Data API is marked as experimental as it's subject to change in the future ([#11230](https://github.com/WordPress/gutenberg/pull/11230)). +- The Reusable blocks Data API is marked as experimental as it's subject to change in the future ([#11230](https://github.com/WordPress/gutenberg/pull/11230)). ## 6.0.1 (2018-10-30) ### Bug Fixes -- Tweak the vanilla style sheet for consistency. -- Fix the "Copy Post Text" button not copying the post text. +- Tweak the vanilla style sheet for consistency. +- Fix the "Copy Post Text" button not copying the post text. ## 6.0.0 (2018-10-29) ### Breaking Changes -- The `labels.name` property has been removed from `MediaPlaceholder` in favor of the new `labels.instructions` prop. -- The `UnsavedChangesWarning` component no longer accepts a `forceIsDirty` prop. -- `mediaDetails` in object passed to `onFileChange` callback of `mediaUpload`. Please use `media_details` property instead. +- The `labels.name` property has been removed from `MediaPlaceholder` in favor of the new `labels.instructions` prop. +- The `UnsavedChangesWarning` component no longer accepts a `forceIsDirty` prop. +- `mediaDetails` in object passed to `onFileChange` callback of `mediaUpload`. Please use `media_details` property instead. ### New Features -- In `MediaPlaceholder`, provide default values for title and instructions labels when allowed type is one of image, audio or video. -- New actions `lockPostSaving` and `unlockPostSaving` were introduced ([#10649](https://github.com/WordPress/gutenberg/pull/10649)). -- New selector `isPostSavingLocked` was introduced ([#10649](https://github.com/WordPress/gutenberg/pull/10649)). +- In `MediaPlaceholder`, provide default values for title and instructions labels when allowed type is one of image, audio or video. +- New actions `lockPostSaving` and `unlockPostSaving` were introduced ([#10649](https://github.com/WordPress/gutenberg/pull/10649)). +- New selector `isPostSavingLocked` was introduced ([#10649](https://github.com/WordPress/gutenberg/pull/10649)). ### Polish -- Add animated logo to preview interstitial screen. -- Tweak the editor styles support. +- Add animated logo to preview interstitial screen. +- Tweak the editor styles support. ### Bug Fixes -- Made preview interstitial text translatable. +- Made preview interstitial text translatable. ## 5.0.1 (2018-10-22) @@ -321,15 +326,15 @@ ### Breaking Changes -- The `checkTemplateValidity` action has been removed. Validity is verified automatically upon block reset. +- The `checkTemplateValidity` action has been removed. Validity is verified automatically upon block reset. ### Deprecations -- `PanelColor` has been deprecated in favor of `PanelColorSettings`. +- `PanelColor` has been deprecated in favor of `PanelColorSettings`. ### New Features -- Added `onClose` prop to `URLPopover` component. +- Added `onClose` prop to `URLPopover` component. ## 4.0.3 (2018-10-18) @@ -337,46 +342,46 @@ ### Breaking Changes -- `getColorName` has been removed. Use `getColorObjectByColorValue` instead. -- `getColorClass` has been renamed. Use `getColorClassName` instead. -- The `value` property in color objects passed by `withColors` has been removed. Use `color` property instead. -- `RichText` `getSettings` prop has been removed. The `unstableGetSettings` prop is available if continued use is required. Unstable APIs are strongly discouraged to be used, and are subject to removal without notice, even as part of a minor release. -- `RichText` `onSetup` prop has been removed. The `unstableOnSetup` prop is available if continued use is required. Unstable APIs are strongly discouraged to be used, and are subject to removal without notice, even as part of a minor release. -- `RichTextProvider` has been removed. Please use `wp.data.select( 'core/editor' )` methods instead. +- `getColorName` has been removed. Use `getColorObjectByColorValue` instead. +- `getColorClass` has been renamed. Use `getColorClassName` instead. +- The `value` property in color objects passed by `withColors` has been removed. Use `color` property instead. +- `RichText` `getSettings` prop has been removed. The `unstableGetSettings` prop is available if continued use is required. Unstable APIs are strongly discouraged to be used, and are subject to removal without notice, even as part of a minor release. +- `RichText` `onSetup` prop has been removed. The `unstableOnSetup` prop is available if continued use is required. Unstable APIs are strongly discouraged to be used, and are subject to removal without notice, even as part of a minor release. +- `RichTextProvider` has been removed. Please use `wp.data.select( 'core/editor' )` methods instead. ### Deprecations -- The `checkTemplateValidity` action has been deprecated. Validity is verified automatically upon block reset. -- The `UnsavedChangesWarning` component `forceIsDirty` prop has been deprecated. +- The `checkTemplateValidity` action has been deprecated. Validity is verified automatically upon block reset. +- The `UnsavedChangesWarning` component `forceIsDirty` prop has been deprecated. ## 3.0.0 (2018-09-05) ### New Features -- Add editor styles support. +- Add editor styles support. ### Breaking Changes -- The `wideAlign` block supports hook has been removed. Use `alignWide` instead. -- `fetchSharedBlocks` action has been removed. Use `fetchReusableBlocks` instead. -- `receiveSharedBlocks` action has been removed. Use `receiveReusableBlocks` instead. -- `saveSharedBlock` action has been removed. Use `saveReusableBlock` instead. -- `deleteSharedBlock` action has been removed. Use `deleteReusableBlock` instead. -- `updateSharedBlockTitle` action has been removed. Use `updateReusableBlockTitle` instead. -- `convertBlockToSaved` action has been removed. Use `convertBlockToReusable` instead. -- `getSharedBlock` selector has been removed. Use `getReusableBlock` instead. -- `isSavingSharedBlock` selector has been removed. Use `isSavingReusableBlock` instead. -- `isFetchingSharedBlock` selector has been removed. Use `isFetchingReusableBlock` instead. -- `getSharedBlocks` selector has been removed. Use `getReusableBlocks` instead. -- `editorMediaUpload` has been removed. Use `mediaUpload` instead. -- Change how required built-ins are polyfilled with Babel 7 ([#9171](https://github.com/WordPress/gutenberg/pull/9171)). If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. -- `DocumentTitle` component has been removed. -- `getDocumentTitle` selector (`core/editor`) has been removed. +- The `wideAlign` block supports hook has been removed. Use `alignWide` instead. +- `fetchSharedBlocks` action has been removed. Use `fetchReusableBlocks` instead. +- `receiveSharedBlocks` action has been removed. Use `receiveReusableBlocks` instead. +- `saveSharedBlock` action has been removed. Use `saveReusableBlock` instead. +- `deleteSharedBlock` action has been removed. Use `deleteReusableBlock` instead. +- `updateSharedBlockTitle` action has been removed. Use `updateReusableBlockTitle` instead. +- `convertBlockToSaved` action has been removed. Use `convertBlockToReusable` instead. +- `getSharedBlock` selector has been removed. Use `getReusableBlock` instead. +- `isSavingSharedBlock` selector has been removed. Use `isSavingReusableBlock` instead. +- `isFetchingSharedBlock` selector has been removed. Use `isFetchingReusableBlock` instead. +- `getSharedBlocks` selector has been removed. Use `getReusableBlocks` instead. +- `editorMediaUpload` has been removed. Use `mediaUpload` instead. +- Change how required built-ins are polyfilled with Babel 7 ([#9171](https://github.com/WordPress/gutenberg/pull/9171)). If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. +- `DocumentTitle` component has been removed. +- `getDocumentTitle` selector (`core/editor`) has been removed. ### Deprecations -- `RichTextProvider` flagged for deprecation. Please use `wp.data.select( 'core/editor' )` methods instead. +- `RichTextProvider` flagged for deprecation. Please use `wp.data.select( 'core/editor' )` methods instead. ### Bug Fixes -- The `PostTextEditor` component will respect its in-progress state edited value, even if the assigned prop value changes. +- The `PostTextEditor` component will respect its in-progress state edited value, even if the assigned prop value changes. diff --git a/packages/editor/package.json b/packages/editor/package.json index 4de4baa4d5dfdd..47be85398b605f 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "9.24.3", + "version": "9.24.4", "description": "Building blocks for WordPress editors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -23,10 +23,11 @@ "react-native": "src/index", "sideEffects": [ "build-style/**", - "!((src|build|build-module)/(components|utils)/**)" + "src/**/*.scss", + "{src,build,build-module}/{index.js,store/index.js,hooks/**}" ], "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/autop": "file:../autop", "@wordpress/blob": "file:../blob", @@ -53,12 +54,11 @@ "@wordpress/rich-text": "file:../rich-text", "@wordpress/server-side-render": "file:../server-side-render", "@wordpress/url": "file:../url", - "@wordpress/viewport": "file:../viewport", "@wordpress/wordcount": "file:../wordcount", "classnames": "^2.2.5", "lodash": "^4.17.19", "memize": "^1.1.0", - "react-autosize-textarea": "^3.0.2", + "react-autosize-textarea": "^7.1.0", "redux-optimist": "^1.0.0", "refx": "^3.0.0", "rememo": "^3.0.0" diff --git a/packages/editor/src/components/autosave-monitor/index.js b/packages/editor/src/components/autosave-monitor/index.js index ee7a9341ea0a2f..79f7f8edbfaa92 100644 --- a/packages/editor/src/components/autosave-monitor/index.js +++ b/packages/editor/src/components/autosave-monitor/index.js @@ -5,6 +5,17 @@ import { Component } from '@wordpress/element'; import { compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; +/** + * AutosaveMonitor invokes `props.autosave()` within at most `interval` seconds after an unsaved change is detected. + * + * The logic is straightforward: a check is performed every `props.interval` seconds. If any changes are detected, `props.autosave()` is called. + * The time between the change and the autosave varies but is no larger than `props.interval` seconds. Refer to the code below for more details, such as + * the specific way of detecting changes. + * + * There are two caveats: + * * If `props.isAutosaveable` happens to be false at a time of checking for changes, the check is retried every second. + * * The timer may be disabled by setting `props.disableIntervalChecks` to `false`. In that mode, any change will immediately trigger `props.autosave()`. + */ export class AutosaveMonitor extends Component { constructor( props ) { super( props ); diff --git a/packages/editor/src/components/convert-to-group-buttons/index.js b/packages/editor/src/components/convert-to-group-buttons/index.js index 21b6e92c1a9f96..d948a1447b50aa 100644 --- a/packages/editor/src/components/convert-to-group-buttons/index.js +++ b/packages/editor/src/components/convert-to-group-buttons/index.js @@ -3,7 +3,7 @@ */ import { MenuItem } from '@wordpress/components'; import { _x } from '@wordpress/i18n'; -import { switchToBlockType } from '@wordpress/blocks'; +import { switchToBlockType, store as blocksStore } from '@wordpress/blocks'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { BlockSettingsMenuControls } from '@wordpress/block-editor'; @@ -60,7 +60,7 @@ export default compose( [ getSelectedBlockClientIds, } = select( 'core/block-editor' ); - const { getGroupingBlockName } = select( 'core/blocks' ); + const { getGroupingBlockName } = select( blocksStore ); const clientIds = getSelectedBlockClientIds(); const groupingBlockName = getGroupingBlockName(); diff --git a/packages/editor/src/components/editor-notices/index.js b/packages/editor/src/components/editor-notices/index.js index fdab17a01b523c..734bb2dae2e015 100644 --- a/packages/editor/src/components/editor-notices/index.js +++ b/packages/editor/src/components/editor-notices/index.js @@ -9,6 +9,7 @@ import { filter } from 'lodash'; import { NoticeList, SnackbarList } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies @@ -52,9 +53,9 @@ export function EditorNotices( { notices, onRemove } ) { export default compose( [ withSelect( ( select ) => ( { - notices: select( 'core/notices' ).getNotices(), + notices: select( noticesStore ).getNotices(), } ) ), withDispatch( ( dispatch ) => ( { - onRemove: dispatch( 'core/notices' ).removeNotice, + onRemove: dispatch( noticesStore ).removeNotice, } ) ), ] )( EditorNotices ); diff --git a/packages/editor/src/components/entities-saved-states/entity-record-item.js b/packages/editor/src/components/entities-saved-states/entity-record-item.js index 44afeb672a5edb..996c9892200570 100644 --- a/packages/editor/src/components/entities-saved-states/entity-record-item.js +++ b/packages/editor/src/components/entities-saved-states/entity-record-item.js @@ -28,6 +28,25 @@ export default function EntityRecordItem( { return parents[ parents.length - 1 ]; }, [] ); + // Handle templates that might use default descriptive titles + const entityRecordTitle = useSelect( + ( select ) => { + if ( 'postType' !== kind || 'wp_template' !== name ) { + return title; + } + + const template = select( 'core' ).getEditedEntityRecord( + kind, + name, + key + ); + return select( 'core/editor' ).__experimentalGetTemplateInfo( + template + ).title; + }, + [ name, kind, title, key ] + ); + const isSelected = useSelect( ( select ) => { const selectedBlockId = select( @@ -50,7 +69,9 @@ export default function EntityRecordItem( { return ( <PanelRow> <CheckboxControl - label={ <strong>{ title || __( 'Untitled' ) }</strong> } + label={ + <strong>{ entityRecordTitle || __( 'Untitled' ) }</strong> + } checked={ checked } onChange={ onChange } /> diff --git a/packages/editor/src/components/entities-saved-states/index.js b/packages/editor/src/components/entities-saved-states/index.js index f7b0ee16ff06dc..d4d31398b94bc2 100644 --- a/packages/editor/src/components/entities-saved-states/index.js +++ b/packages/editor/src/components/entities-saved-states/index.js @@ -6,7 +6,7 @@ import { some, groupBy } from 'lodash'; /** * WordPress dependencies */ -import { Button } from '@wordpress/components'; +import { Button, withFocusReturn } from '@wordpress/components'; import { __, sprintf, _n } from '@wordpress/i18n'; import { useSelect, useDispatch } from '@wordpress/data'; import { useState, useCallback } from '@wordpress/element'; @@ -30,18 +30,24 @@ const PLACEHOLDER_PHRASES = { // 0 is a back up, but should never be observed. 0: __( 'There are no changes.' ), /* translators: placeholders represent pre-translated singular/plural entity names (page, post, template, site, etc.) */ - 1: __( 'Changes have been made to your %s.' ), + 1: __( 'The following changes have been made to your %s.' ), /* translators: placeholders represent pre-translated singular/plural entity names (page, post, template, site, etc.) */ - 2: __( 'Changes have been made to your %1$s and %2$s.' ), + 2: __( 'The following changes have been made to your %1$s and %2$s.' ), /* translators: placeholders represent pre-translated singular/plural entity names (page, post, template, site, etc.) */ - 3: __( 'Changes have been made to your %1$s, %2$s, and %3$s.' ), + 3: __( + 'The following changes have been made to your %1$s, %2$s, and %3$s.' + ), /* translators: placeholders represent pre-translated singular/plural entity names (page, post, template, site, etc.) */ - 4: __( 'Changes have been made to your %1$s, %2$s, %3$s, and %4$s.' ), + 4: __( + 'The following changes have been made to your %1$s, %2$s, %3$s, and %4$s.' + ), /* translators: placeholders represent pre-translated singular/plural entity names (page, post, template, site, etc.) */ - 5: __( 'Changes have been made to your %1$s, %2$s, %3$s, %4$s, and %5$s.' ), + 5: __( + 'The following changes have been made to your %1$s, %2$s, %3$s, %4$s, and %5$s.' + ), }; -export default function EntitiesSavedStates( { isOpen, close } ) { +function EntitiesSavedStates( { isOpen, close } ) { const { dirtyEntityRecords } = useSelect( ( select ) => { return { dirtyEntityRecords: select( @@ -69,7 +75,7 @@ export default function EntitiesSavedStates( { isOpen, close } ) { const placeholderPhrase = PLACEHOLDER_PHRASES[ entityNamesForPrompt.length ] || // Fallback for edge case that should not be observed (more than 5 entity types edited). - __( 'Changes have been made to multiple entity types.' ); + __( 'The following changes have been made to multiple entities.' ); // eslint-disable-next-line @wordpress/valid-sprintf const promptPhrase = sprintf( placeholderPhrase, ...entityNamesForPrompt ); @@ -114,9 +120,6 @@ export default function EntitiesSavedStates( { isOpen, close } ) { } ); }; - const [ isReviewing, setIsReviewing ] = useState( false ); - const toggleIsReviewing = () => setIsReviewing( ( value ) => ! value ); - // Explicitly define this with no argument passed. Using `close` on // its own will use the event object in place of the expected saved entities. const dismissPanel = useCallback( () => close(), [ close ] ); @@ -146,31 +149,21 @@ export default function EntitiesSavedStates( { isOpen, close } ) { <div className="entities-saved-states__text-prompt"> <strong>{ __( 'Are you ready to save?' ) }</strong> <p>{ promptPhrase }</p> - <p> - <Button - onClick={ toggleIsReviewing } - isLink - className="entities-saved-states__review-changes-button" - > - { isReviewing - ? __( 'Hide changes.' ) - : __( 'Review changes.' ) } - </Button> - </p> </div> - { isReviewing && - partitionedSavables.map( ( list ) => { - return ( - <EntityTypeList - key={ list[ 0 ].name } - list={ list } - closePanel={ dismissPanel } - unselectedEntities={ unselectedEntities } - setUnselectedEntities={ setUnselectedEntities } - /> - ); - } ) } + { partitionedSavables.map( ( list ) => { + return ( + <EntityTypeList + key={ list[ 0 ].name } + list={ list } + closePanel={ dismissPanel } + unselectedEntities={ unselectedEntities } + setUnselectedEntities={ setUnselectedEntities } + /> + ); + } ) } </div> ) : null; } + +export default withFocusReturn( EntitiesSavedStates ); diff --git a/packages/editor/src/components/entities-saved-states/style.scss b/packages/editor/src/components/entities-saved-states/style.scss index 9a347550fc9ad9..e2e9203dec07ba 100644 --- a/packages/editor/src/components/entities-saved-states/style.scss +++ b/packages/editor/src/components/entities-saved-states/style.scss @@ -18,7 +18,6 @@ } @include break-medium() { - z-index: z-index(".entities-saved-states__panel {greater than small}"); top: $admin-bar-height; left: auto; width: $sidebar-width; @@ -57,7 +56,6 @@ } .entities-saved-states__text-prompt { - border-bottom: $border-width solid $gray-300; padding: $grid-unit-20; padding-bottom: $grid-unit-05; } diff --git a/packages/editor/src/components/global-keyboard-shortcuts/register-shortcuts.js b/packages/editor/src/components/global-keyboard-shortcuts/register-shortcuts.js index 4879a83d1d5ddd..ed65b49d3cc8c3 100644 --- a/packages/editor/src/components/global-keyboard-shortcuts/register-shortcuts.js +++ b/packages/editor/src/components/global-keyboard-shortcuts/register-shortcuts.js @@ -5,10 +5,11 @@ import { useEffect } from '@wordpress/element'; import { useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { BlockEditorKeyboardShortcuts } from '@wordpress/block-editor'; +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; function EditorKeyboardShortcutsRegister() { // Registering the shortcuts - const { registerShortcut } = useDispatch( 'core/keyboard-shortcuts' ); + const { registerShortcut } = useDispatch( keyboardShortcutsStore ); useEffect( () => { registerShortcut( { name: 'core/editor/save', diff --git a/packages/editor/src/components/local-autosave-monitor/index.js b/packages/editor/src/components/local-autosave-monitor/index.js index aaf697bc70d946..80573eec4598ba 100644 --- a/packages/editor/src/components/local-autosave-monitor/index.js +++ b/packages/editor/src/components/local-autosave-monitor/index.js @@ -11,6 +11,7 @@ import { ifCondition, usePrevious } from '@wordpress/compose'; import { useSelect, useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { parse } from '@wordpress/blocks'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies @@ -62,7 +63,7 @@ function useAutosaveNotice() { [] ); - const { createWarningNotice, removeNotice } = useDispatch( 'core/notices' ); + const { createWarningNotice, removeNotice } = useDispatch( noticesStore ); const { editPost, resetEditorBlocks } = useDispatch( 'core/editor' ); useEffect( () => { diff --git a/packages/editor/src/components/post-author/check.js b/packages/editor/src/components/post-author/check.js index 8ee69ff4826c99..1826da16a76ef8 100644 --- a/packages/editor/src/components/post-author/check.js +++ b/packages/editor/src/components/post-author/check.js @@ -19,7 +19,7 @@ export function PostAuthorCheck( { authors, children, } ) { - if ( ! hasAssignAuthorAction || ! authors || authors.length < 2 ) { + if ( ! hasAssignAuthorAction || ! authors || 1 >= authors.length ) { return null; } @@ -40,7 +40,7 @@ export default compose( [ false ), postType: select( 'core/editor' ).getCurrentPostType(), - authors: select( 'core' ).getUsers( { who: 'authors' } ), + authors: select( 'core' ).getAuthors(), }; } ), withInstanceId, diff --git a/packages/editor/src/components/post-author/index.js b/packages/editor/src/components/post-author/index.js index 889f5dcf0c74e0..ea0e8909ab2fd5 100644 --- a/packages/editor/src/components/post-author/index.js +++ b/packages/editor/src/components/post-author/index.js @@ -21,18 +21,20 @@ function PostAuthor() { const { authorId, isLoading, authors, postAuthor } = useSelect( ( select ) => { - const { getUser, getUsers, isResolving } = select( 'core' ); + const { __unstableGetAuthor, getAuthors, isResolving } = select( + 'core' + ); const { getEditedPostAttribute } = select( 'core/editor' ); - const author = getUser( getEditedPostAttribute( 'author' ) ); + const author = __unstableGetAuthor( + getEditedPostAttribute( 'author' ) + ); const query = ! fieldValue || '' === fieldValue ? {} : { search: fieldValue }; return { authorId: getEditedPostAttribute( 'author' ), postAuthor: author, - authors: getUsers( { who: 'authors', ...query } ), - isLoading: isResolving( 'core', 'getUsers', [ - { search: fieldValue, who: 'authors' }, - ] ), + authors: getAuthors( query ), + isLoading: isResolving( 'core', 'getAuthors', [ query ] ), }; }, [ fieldValue ] diff --git a/packages/editor/src/components/post-format/index.js b/packages/editor/src/components/post-format/index.js index 65f83afe8b6b3c..57620a32b9025e 100644 --- a/packages/editor/src/components/post-format/index.js +++ b/packages/editor/src/components/post-format/index.js @@ -16,18 +16,30 @@ import { useInstanceId } from '@wordpress/compose'; */ import PostFormatCheck from './check'; +// All WP post formats, sorted alphabetically by translated name. export const POST_FORMATS = [ { id: 'aside', caption: __( 'Aside' ) }, + { id: 'audio', caption: __( 'Audio' ) }, + { id: 'chat', caption: __( 'Chat' ) }, { id: 'gallery', caption: __( 'Gallery' ) }, - { id: 'link', caption: __( 'Link' ) }, { id: 'image', caption: __( 'Image' ) }, + { id: 'link', caption: __( 'Link' ) }, { id: 'quote', caption: __( 'Quote' ) }, { id: 'standard', caption: __( 'Standard' ) }, { id: 'status', caption: __( 'Status' ) }, { id: 'video', caption: __( 'Video' ) }, - { id: 'audio', caption: __( 'Audio' ) }, - { id: 'chat', caption: __( 'Chat' ) }, -]; +].sort( ( a, b ) => { + const normalizedA = a.caption.toUpperCase(); + const normalizedB = b.caption.toUpperCase(); + + if ( normalizedA < normalizedB ) { + return -1; + } + if ( normalizedA > normalizedB ) { + return 1; + } + return 0; +} ); export default function PostFormat() { const instanceId = useInstanceId( PostFormat ); diff --git a/packages/editor/src/components/post-locked-modal/index.js b/packages/editor/src/components/post-locked-modal/index.js index 99f32e3ae147c3..679d02d3ee3b13 100644 --- a/packages/editor/src/components/post-locked-modal/index.js +++ b/packages/editor/src/components/post-locked-modal/index.js @@ -8,11 +8,11 @@ import { get } from 'lodash'; */ import { __, sprintf } from '@wordpress/i18n'; import { Modal, Button } from '@wordpress/components'; -import { withSelect, withDispatch } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { addQueryArgs } from '@wordpress/url'; -import { Component } from '@wordpress/element'; +import { useEffect } from '@wordpress/element'; import { addAction, removeAction } from '@wordpress/hooks'; -import { compose, withGlobalEvents, withInstanceId } from '@wordpress/compose'; +import { useInstanceId } from '@wordpress/compose'; /** * Internal dependencies @@ -20,220 +20,19 @@ import { compose, withGlobalEvents, withInstanceId } from '@wordpress/compose'; import { getWPAdminURL } from '../../utils/url'; import PostPreviewButton from '../post-preview-button'; -class PostLockedModal extends Component { - constructor() { - super( ...arguments ); - - this.sendPostLock = this.sendPostLock.bind( this ); - this.receivePostLock = this.receivePostLock.bind( this ); - this.releasePostLock = this.releasePostLock.bind( this ); - } - - componentDidMount() { - const hookName = this.getHookName(); - - // Details on these events on the Heartbeat API docs - // https://developer.wordpress.org/plugins/javascript/heartbeat-api/ - addAction( 'heartbeat.send', hookName, this.sendPostLock ); - addAction( 'heartbeat.tick', hookName, this.receivePostLock ); - } - - componentWillUnmount() { - const hookName = this.getHookName(); - - removeAction( 'heartbeat.send', hookName ); - removeAction( 'heartbeat.tick', hookName ); - } - - /** - * Returns a `@wordpress/hooks` hook name specific to the instance of the - * component. - * - * @return {string} Hook name prefix. - */ - getHookName() { - const { instanceId } = this.props; - return 'core/editor/post-locked-modal-' + instanceId; - } - - /** - * Keep the lock refreshed. - * - * When the user does not send a heartbeat in a heartbeat-tick - * the user is no longer editing and another user can start editing. - * - * @param {Object} data Data to send in the heartbeat request. - */ - sendPostLock( data ) { - const { isLocked, activePostLock, postId } = this.props; - if ( isLocked ) { - return; - } - - data[ 'wp-refresh-post-lock' ] = { - lock: activePostLock, - post_id: postId, - }; - } - - /** - * Refresh post locks: update the lock string or show the dialog if somebody has taken over editing. - * - * @param {Object} data Data received in the heartbeat request - */ - receivePostLock( data ) { - if ( ! data[ 'wp-refresh-post-lock' ] ) { - return; - } - - const { autosave, updatePostLock } = this.props; - const received = data[ 'wp-refresh-post-lock' ]; - if ( received.lock_error ) { - // Auto save and display the takeover modal. - autosave(); - updatePostLock( { - isLocked: true, - isTakeover: true, - user: { - avatar: received.lock_error.avatar_src, - }, - } ); - } else if ( received.new_lock ) { - updatePostLock( { - isLocked: false, - activePostLock: received.new_lock, - } ); - } - } - - /** - * Unlock the post before the window is exited. - */ - releasePostLock() { - const { isLocked, activePostLock, postLockUtils, postId } = this.props; - if ( isLocked || ! activePostLock ) { - return; - } - - const data = new window.FormData(); - data.append( 'action', 'wp-remove-post-lock' ); - data.append( '_wpnonce', postLockUtils.unlockNonce ); - data.append( 'post_ID', postId ); - data.append( 'active_post_lock', activePostLock ); - - if ( window.navigator.sendBeacon ) { - window.navigator.sendBeacon( postLockUtils.ajaxUrl, data ); - } else { - const xhr = new window.XMLHttpRequest(); - xhr.open( 'POST', postLockUtils.ajaxUrl, false ); - xhr.send( data ); - } - } - - render() { - const { - user, - postId, - isLocked, - isTakeover, - postLockUtils, - postType, - } = this.props; - if ( ! isLocked ) { - return null; - } - - const userDisplayName = user.name; - const userAvatar = user.avatar; - - const unlockUrl = addQueryArgs( 'post.php', { - 'get-post-lock': '1', - lockKey: true, - post: postId, - action: 'edit', - _wpnonce: postLockUtils.nonce, - } ); - const allPostsUrl = getWPAdminURL( 'edit.php', { - post_type: get( postType, [ 'slug' ] ), - } ); - const allPostsLabel = __( 'Exit the Editor' ); - return ( - <Modal - title={ - isTakeover - ? __( 'Someone else has taken over this post.' ) - : __( 'This post is already being edited.' ) - } - focusOnMount={ true } - shouldCloseOnClickOutside={ false } - shouldCloseOnEsc={ false } - isDismissible={ false } - className="editor-post-locked-modal" - > - { !! userAvatar && ( - <img - src={ userAvatar } - alt={ __( 'Avatar' ) } - className="editor-post-locked-modal__avatar" - /> - ) } - { !! isTakeover && ( - <div> - <div> - { userDisplayName - ? sprintf( - /* translators: %s: user's display name */ - __( - '%s now has editing control of this post. Don’t worry, your changes up to this moment have been saved.' - ), - userDisplayName - ) - : __( - 'Another user now has editing control of this post. Don’t worry, your changes up to this moment have been saved.' - ) } - </div> - - <div className="editor-post-locked-modal__buttons"> - <Button isPrimary href={ allPostsUrl }> - { allPostsLabel } - </Button> - </div> - </div> - ) } - { ! isTakeover && ( - <div> - <div> - { userDisplayName - ? sprintf( - /* translators: %s: user's display name */ - __( - '%s is currently working on this post, which means you cannot make changes, unless you take over.' - ), - userDisplayName - ) - : __( - 'Another user is currently working on this post, which means you cannot make changes, unless you take over.' - ) } - </div> - - <div className="editor-post-locked-modal__buttons"> - <Button isSecondary href={ allPostsUrl }> - { allPostsLabel } - </Button> - <PostPreviewButton /> - <Button isPrimary href={ unlockUrl }> - { __( 'Take Over' ) } - </Button> - </div> - </div> - ) } - </Modal> - ); - } -} - -export default compose( - withSelect( ( select ) => { +export default function PostLockedModal() { + const instanceId = useInstanceId( PostLockedModal ); + const hookName = 'core/editor/post-locked-modal-' + instanceId; + const { autosave, updatePostLock } = useDispatch( 'core/editor' ); + const { + isLocked, + isTakeover, + user, + postId, + postLockUtils, + activePostLock, + postType, + } = useSelect( ( select ) => { const { isPostLocked, isPostLockTakeover, @@ -253,16 +52,181 @@ export default compose( activePostLock: getActivePostLock(), postType: getPostType( getEditedPostAttribute( 'type' ) ), }; - } ), - withDispatch( ( dispatch ) => { - const { autosave, updatePostLock } = dispatch( 'core/editor' ); - return { - autosave, - updatePostLock, + } ); + + useEffect( () => { + /** + * Keep the lock refreshed. + * + * When the user does not send a heartbeat in a heartbeat-tick + * the user is no longer editing and another user can start editing. + * + * @param {Object} data Data to send in the heartbeat request. + */ + function sendPostLock( data ) { + if ( isLocked ) { + return; + } + + data[ 'wp-refresh-post-lock' ] = { + lock: activePostLock, + post_id: postId, + }; + } + + /** + * Refresh post locks: update the lock string or show the dialog if somebody has taken over editing. + * + * @param {Object} data Data received in the heartbeat request + */ + function receivePostLock( data ) { + if ( ! data[ 'wp-refresh-post-lock' ] ) { + return; + } + + const received = data[ 'wp-refresh-post-lock' ]; + if ( received.lock_error ) { + // Auto save and display the takeover modal. + autosave(); + updatePostLock( { + isLocked: true, + isTakeover: true, + user: { + avatar: received.lock_error.avatar_src, + }, + } ); + } else if ( received.new_lock ) { + updatePostLock( { + isLocked: false, + activePostLock: received.new_lock, + } ); + } + } + + /** + * Unlock the post before the window is exited. + */ + function releasePostLock() { + if ( isLocked || ! activePostLock ) { + return; + } + + const data = new window.FormData(); + data.append( 'action', 'wp-remove-post-lock' ); + data.append( '_wpnonce', postLockUtils.unlockNonce ); + data.append( 'post_ID', postId ); + data.append( 'active_post_lock', activePostLock ); + + if ( window.navigator.sendBeacon ) { + window.navigator.sendBeacon( postLockUtils.ajaxUrl, data ); + } else { + const xhr = new window.XMLHttpRequest(); + xhr.open( 'POST', postLockUtils.ajaxUrl, false ); + xhr.send( data ); + } + } + + // Details on these events on the Heartbeat API docs + // https://developer.wordpress.org/plugins/javascript/heartbeat-api/ + addAction( 'heartbeat.send', hookName, sendPostLock ); + addAction( 'heartbeat.tick', hookName, receivePostLock ); + window.addEventListener( 'beforeunload', releasePostLock ); + + return () => { + removeAction( 'heartbeat.send', hookName ); + removeAction( 'heartbeat.tick', hookName ); + window.removeEventListener( 'beforeunload', releasePostLock ); }; - } ), - withInstanceId, - withGlobalEvents( { - beforeunload: 'releasePostLock', - } ) -)( PostLockedModal ); + }, [] ); + + if ( ! isLocked ) { + return null; + } + + const userDisplayName = user.name; + const userAvatar = user.avatar; + + const unlockUrl = addQueryArgs( 'post.php', { + 'get-post-lock': '1', + lockKey: true, + post: postId, + action: 'edit', + _wpnonce: postLockUtils.nonce, + } ); + const allPostsUrl = getWPAdminURL( 'edit.php', { + post_type: get( postType, [ 'slug' ] ), + } ); + const allPostsLabel = __( 'Exit the Editor' ); + return ( + <Modal + title={ + isTakeover + ? __( 'Someone else has taken over this post.' ) + : __( 'This post is already being edited.' ) + } + focusOnMount={ true } + shouldCloseOnClickOutside={ false } + shouldCloseOnEsc={ false } + isDismissible={ false } + className="editor-post-locked-modal" + > + { !! userAvatar && ( + <img + src={ userAvatar } + alt={ __( 'Avatar' ) } + className="editor-post-locked-modal__avatar" + /> + ) } + { !! isTakeover && ( + <div> + <div> + { userDisplayName + ? sprintf( + /* translators: %s: user's display name */ + __( + '%s now has editing control of this post. Don’t worry, your changes up to this moment have been saved.' + ), + userDisplayName + ) + : __( + 'Another user now has editing control of this post. Don’t worry, your changes up to this moment have been saved.' + ) } + </div> + + <div className="editor-post-locked-modal__buttons"> + <Button isPrimary href={ allPostsUrl }> + { allPostsLabel } + </Button> + </div> + </div> + ) } + { ! isTakeover && ( + <div> + <div> + { userDisplayName + ? sprintf( + /* translators: %s: user's display name */ + __( + '%s is currently working on this post, which means you cannot make changes, unless you take over.' + ), + userDisplayName + ) + : __( + 'Another user is currently working on this post, which means you cannot make changes, unless you take over.' + ) } + </div> + + <div className="editor-post-locked-modal__buttons"> + <Button isSecondary href={ allPostsUrl }> + { allPostsLabel } + </Button> + <PostPreviewButton /> + <Button isPrimary href={ unlockUrl }> + { __( 'Take Over' ) } + </Button> + </div> + </div> + ) } + </Modal> + ); +} diff --git a/packages/editor/src/components/post-saved-state/index.js b/packages/editor/src/components/post-saved-state/index.js index bc98454a9767ec..1103b0f7468785 100644 --- a/packages/editor/src/components/post-saved-state/index.js +++ b/packages/editor/src/components/post-saved-state/index.js @@ -6,7 +6,10 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Animate, Button } from '@wordpress/components'; +import { + __unstableGetAnimateClassName as getAnimateClassName, + Button, +} from '@wordpress/components'; import { usePrevious, useViewportMatch } from '@wordpress/compose'; import { useDispatch, useSelect } from '@wordpress/data'; import { useEffect, useState } from '@wordpress/element'; @@ -73,8 +76,7 @@ export default function PostSavedState( { isSaveable: isEditedPostSaveable(), isScheduled: isCurrentPostScheduled(), hasPublishAction: - getCurrentPost()?.[ '_links' ]?.[ 'wp:action-publish' ] ?? - false, + getCurrentPost()?._links?.[ 'wp:action-publish' ] ?? false, }; }, [ forceIsDirty, forceIsSaving ] @@ -101,19 +103,20 @@ export default function PostSavedState( { // TODO: Classes generation should be common across all return // paths of this function, including proper naming convention for // the "Save Draft" button. - const classes = classnames( 'editor-post-saved-state', 'is-saving', { - 'is-autosaving': isAutosaving, - } ); + const classes = classnames( + 'editor-post-saved-state', + 'is-saving', + getAnimateClassName( { type: 'loading' } ), + { + 'is-autosaving': isAutosaving, + } + ); return ( - <Animate type="loading"> - { ( { className: animateClassName } ) => ( - <span className={ classnames( classes, animateClassName ) }> - <Icon icon={ cloud } /> - { isAutosaving ? __( 'Autosaving' ) : __( 'Saving' ) } - </span> - ) } - </Animate> + <span className={ classes }> + <Icon icon={ cloud } /> + { isAutosaving ? __( 'Autosaving' ) : __( 'Saving' ) } + </span> ); } diff --git a/packages/editor/src/components/post-schedule/index.js b/packages/editor/src/components/post-schedule/index.js index 549a12074244bc..06266f1bd68d9f 100644 --- a/packages/editor/src/components/post-schedule/index.js +++ b/packages/editor/src/components/post-schedule/index.js @@ -5,12 +5,10 @@ import { __experimentalGetSettings } from '@wordpress/date'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { DateTimePicker } from '@wordpress/components'; +import { useRef } from '@wordpress/element'; export function PostSchedule( { date, onUpdateDate } ) { - const onChange = ( newDate ) => { - onUpdateDate( newDate ); - document.activeElement.blur(); - }; + const ref = useRef(); const settings = __experimentalGetSettings(); // To know if the current timezone is a 12 hour time with look for "a" in the time format // We also make sure this a is not escaped by a "/" @@ -23,9 +21,15 @@ export function PostSchedule( { date, onUpdateDate } ) { .join( '' ) // Reverse the string and test for "a" not followed by a slash ); + function onChange( newDate ) { + onUpdateDate( newDate ); + const { ownerDocument } = ref.current; + ownerDocument.activeElement.blur(); + } + return ( <DateTimePicker - key="date-time-picker" + ref={ ref } currentDate={ date } onChange={ onChange } is12Hour={ is12HourTime } diff --git a/packages/editor/src/components/post-title/index.js b/packages/editor/src/components/post-title/index.js index 5c0066c09fd527..493931b5003e3e 100644 --- a/packages/editor/src/components/post-title/index.js +++ b/packages/editor/src/components/post-title/index.js @@ -1,19 +1,19 @@ /** * External dependencies */ -import Textarea from 'react-autosize-textarea'; +import TextareaAutosize from 'react-autosize-textarea'; import classnames from 'classnames'; /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Component } from '@wordpress/element'; +import { useEffect, useRef, useState } from '@wordpress/element'; import { decodeEntities } from '@wordpress/html-entities'; import { ENTER } from '@wordpress/keycodes'; -import { withSelect, withDispatch } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { VisuallyHidden } from '@wordpress/components'; -import { withInstanceId, compose } from '@wordpress/compose'; +import { useInstanceId } from '@wordpress/compose'; import { pasteHandler } from '@wordpress/blocks'; /** @@ -26,44 +26,89 @@ import PostTypeSupportCheck from '../post-type-support-check'; */ const REGEXP_NEWLINES = /[\r\n]+/g; -class PostTitle extends Component { - constructor() { - super( ...arguments ); +export default function PostTitle() { + const instanceId = useInstanceId( PostTitle ); + const ref = useRef(); + const [ isSelected, setIsSelected ] = useState( false ); + const { editPost } = useDispatch( 'core/editor' ); + const { + insertDefaultBlock, + clearSelectedBlock, + insertBlocks, + } = useDispatch( 'core/block-editor' ); + const { + isCleanNewPost, + title, + placeholder, + isFocusMode, + hasFixedToolbar, + } = useSelect( ( select ) => { + const { + getEditedPostAttribute, + isCleanNewPost: _isCleanNewPost, + } = select( 'core/editor' ); + const { getSettings } = select( 'core/block-editor' ); + const { + titlePlaceholder, + focusMode, + hasFixedToolbar: _hasFixedToolbar, + } = getSettings(); + + return { + isCleanNewPost: _isCleanNewPost(), + title: getEditedPostAttribute( 'title' ), + placeholder: titlePlaceholder, + isFocusMode: focusMode, + hasFixedToolbar: _hasFixedToolbar, + }; + } ); + + useEffect( () => { + const { ownerDocument } = ref.current; + const { activeElement, body } = ownerDocument; + + // Only autofocus the title when the post is entirely empty. This should + // only happen for a new post, which means we focus the title on new + // post so the author can start typing right away, without needing to + // click anything. + if ( isCleanNewPost && ( ! activeElement || body === activeElement ) ) { + ref.current.focus(); + } + }, [ isCleanNewPost ] ); - this.onChange = this.onChange.bind( this ); - this.onSelect = this.onSelect.bind( this ); - this.onUnselect = this.onUnselect.bind( this ); - this.onKeyDown = this.onKeyDown.bind( this ); - this.onPaste = this.onPaste.bind( this ); + function onEnterPress() { + insertDefaultBlock( undefined, undefined, 0 ); + } - this.state = { - isSelected: false, - }; + function onInsertBlockAfter( blocks ) { + insertBlocks( blocks, 0 ); } - onSelect() { - this.setState( { isSelected: true } ); - this.props.clearSelectedBlock(); + function onUpdate( newTitle ) { + editPost( { title: newTitle } ); } - onUnselect() { - this.setState( { isSelected: false } ); + function onSelect() { + setIsSelected( true ); + clearSelectedBlock(); } - onChange( event ) { - const newTitle = event.target.value.replace( REGEXP_NEWLINES, ' ' ); - this.props.onUpdate( newTitle ); + function onUnselect() { + setIsSelected( false ); } - onKeyDown( event ) { + function onChange( event ) { + onUpdate( event.target.value.replace( REGEXP_NEWLINES, ' ' ) ); + } + + function onKeyDown( event ) { if ( event.keyCode === ENTER ) { event.preventDefault(); - this.props.onEnterPress(); + onEnterPress(); } } - onPaste( event ) { - const { title, onInsertBlockAfter, onUpdate } = this.props; + function onPaste( event ) { const clipboardData = event.clipboardData; let plainText = ''; @@ -113,105 +158,41 @@ class PostTitle extends Component { } } - render() { - const { - hasFixedToolbar, - isCleanNewPost, - isFocusMode, - instanceId, - placeholder, - title, - } = this.props; - const { isSelected } = this.state; - - // The wp-block className is important for editor styles. - // This same block is used in both the visual and the code editor. - const className = classnames( - 'wp-block editor-post-title editor-post-title__block', - { - 'is-selected': isSelected, - 'is-focus-mode': isFocusMode, - 'has-fixed-toolbar': hasFixedToolbar, - } - ); - const decodedPlaceholder = decodeEntities( placeholder ); - - return ( - <PostTypeSupportCheck supportKeys="title"> - <div className={ className }> - <VisuallyHidden - as="label" - htmlFor={ `post-title-${ instanceId }` } - > - { decodedPlaceholder || __( 'Add title' ) } - </VisuallyHidden> - <Textarea - id={ `post-title-${ instanceId }` } - className="editor-post-title__input" - value={ title } - onChange={ this.onChange } - placeholder={ decodedPlaceholder || __( 'Add title' ) } - onFocus={ this.onSelect } - onBlur={ this.onUnselect } - onKeyDown={ this.onKeyDown } - onKeyPress={ this.onUnselect } - onPaste={ this.onPaste } - /* - Only autofocus the title when the post is entirely empty. - This should only happen for a new post, which means we - focus the title on new post so the author can start typing - right away, without needing to click anything. - */ - /* eslint-disable jsx-a11y/no-autofocus */ - autoFocus={ - ( document.body === document.activeElement || - ! document.activeElement ) && - isCleanNewPost - } - /* eslint-enable jsx-a11y/no-autofocus */ - /> - </div> - </PostTypeSupportCheck> - ); - } -} - -const applyWithSelect = withSelect( ( select ) => { - const { getEditedPostAttribute, isCleanNewPost } = select( 'core/editor' ); - const { getSettings } = select( 'core/block-editor' ); - const { titlePlaceholder, focusMode, hasFixedToolbar } = getSettings(); - - return { - isCleanNewPost: isCleanNewPost(), - title: getEditedPostAttribute( 'title' ), - placeholder: titlePlaceholder, - isFocusMode: focusMode, - hasFixedToolbar, - }; -} ); - -const applyWithDispatch = withDispatch( ( dispatch ) => { - const { insertDefaultBlock, clearSelectedBlock, insertBlocks } = dispatch( - 'core/block-editor' + // The wp-block className is important for editor styles. + // This same block is used in both the visual and the code editor. + const className = classnames( + 'wp-block editor-post-title editor-post-title__block', + { + 'is-selected': isSelected, + 'is-focus-mode': isFocusMode, + 'has-fixed-toolbar': hasFixedToolbar, + } ); - const { editPost } = dispatch( 'core/editor' ); - - return { - onEnterPress() { - insertDefaultBlock( undefined, undefined, 0 ); - }, - onInsertBlockAfter( blocks ) { - insertBlocks( blocks, 0 ); - }, - onUpdate( title ) { - editPost( { title } ); - }, - clearSelectedBlock, - }; -} ); - -export default compose( - applyWithSelect, - applyWithDispatch, - withInstanceId -)( PostTitle ); + const decodedPlaceholder = decodeEntities( placeholder ); + + return ( + <PostTypeSupportCheck supportKeys="title"> + <div className={ className }> + <VisuallyHidden + as="label" + htmlFor={ `post-title-${ instanceId }` } + > + { decodedPlaceholder || __( 'Add title' ) } + </VisuallyHidden> + <TextareaAutosize + ref={ ref } + id={ `post-title-${ instanceId }` } + className="editor-post-title__input" + value={ title } + onChange={ onChange } + placeholder={ decodedPlaceholder || __( 'Add title' ) } + onFocus={ onSelect } + onBlur={ onUnselect } + onKeyDown={ onKeyDown } + onKeyPress={ onUnselect } + onPaste={ onPaste } + /> + </div> + </PostTypeSupportCheck> + ); +} diff --git a/packages/editor/src/components/post-trash/check.js b/packages/editor/src/components/post-trash/check.js index dda336346a0cb8..bd4eb2f9efce62 100644 --- a/packages/editor/src/components/post-trash/check.js +++ b/packages/editor/src/components/post-trash/check.js @@ -18,7 +18,7 @@ export default withSelect( ( select ) => { const { getPostType, canUser } = select( 'core' ); const postId = getCurrentPostId(); const postType = getPostType( getCurrentPostType() ); - const resource = postType?.[ 'rest_base' ] || ''; + const resource = postType?.rest_base || ''; // eslint-disable-line camelcase return { isNew: isEditedPostNew(), diff --git a/packages/editor/src/components/post-visibility/style.scss b/packages/editor/src/components/post-visibility/style.scss index 78eddc859e6ac2..d43341ac3d1289 100644 --- a/packages/editor/src/components/post-visibility/style.scss +++ b/packages/editor/src/components/post-visibility/style.scss @@ -21,16 +21,19 @@ .editor-post-visibility__dialog-info { margin-top: 0; - margin-left: 28px; + margin-left: $grid-unit-40; } // Remove bottom margin on the last label only. .editor-post-visibility__choice:last-child .editor-post-visibility__dialog-info { margin-bottom: 0; } +} +.editor-post-visibility__dialog-password { .editor-post-visibility__dialog-password-input[type="text"] { @include input-control; - margin-left: 28px; + margin-left: $grid-unit * 4.5; + margin-top: $grid-unit-10; } } diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index ae930766bc769c..2f056ea3dbd727 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -1,145 +1,79 @@ -/** - * External dependencies - */ -import { map, pick, defaultTo, flatten, partialRight } from 'lodash'; -import memize from 'memize'; - /** * WordPress dependencies */ -import { compose } from '@wordpress/compose'; -import { Component } from '@wordpress/element'; -import { withDispatch, withSelect } from '@wordpress/data'; +import { useEffect, useLayoutEffect, useMemo } from '@wordpress/element'; +import { useDispatch, useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { EntityProvider } from '@wordpress/core-data'; import { BlockEditorProvider, BlockContextProvider, - __unstableEditorStyles as EditorStyles, } from '@wordpress/block-editor'; -import apiFetch from '@wordpress/api-fetch'; -import { addQueryArgs } from '@wordpress/url'; -import { decodeEntities } from '@wordpress/html-entities'; import { ReusableBlocksMenuItems } from '@wordpress/reusable-blocks'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies */ import withRegistryProvider from './with-registry-provider'; -import { mediaUpload } from '../../utils'; import ConvertToGroupButtons from '../convert-to-group-buttons'; - -/** - * Fetches link suggestions from the API. This function is an exact copy of a function found at: - * - * packages/edit-navigation/src/index.js - * - * It seems like there is no suitable package to import this from. Ideally it would be either part of core-data. - * Until we refactor it, just copying the code is the simplest solution. - * - * @param {string} search - * @param {Object} [searchArguments] - * @param {number} [searchArguments.isInitialSuggestions] - * @param {number} [searchArguments.type] - * @param {number} [searchArguments.subtype] - * @param {number} [searchArguments.page] - * @param {Object} [editorSettings] - * @param {boolean} [editorSettings.disablePostFormats=false] - * @return {Promise<Object[]>} List of suggestions - */ - -const fetchLinkSuggestions = async ( - search, - { isInitialSuggestions, type, subtype, page, perPage: perPageArg } = {}, - { disablePostFormats = false } = {} -) => { - const perPage = perPageArg || isInitialSuggestions ? 3 : 20; - - const queries = []; - - if ( ! type || type === 'post' ) { - queries.push( - apiFetch( { - path: addQueryArgs( '/wp/v2/search', { - search, - page, - per_page: perPage, - type: 'post', - subtype, - } ), - } ).catch( () => [] ) // fail by returning no results - ); - } - - if ( ! type || type === 'term' ) { - queries.push( - apiFetch( { - path: addQueryArgs( '/wp/v2/search', { - search, - page, - per_page: perPage, - type: 'term', - subtype, - } ), - } ).catch( () => [] ) - ); - } - - if ( ! disablePostFormats && ( ! type || type === 'post-format' ) ) { - queries.push( - apiFetch( { - path: addQueryArgs( '/wp/v2/search', { - search, - page, - per_page: perPage, - type: 'post-format', - subtype, - } ), - } ).catch( () => [] ) - ); - } - - return Promise.all( queries ).then( ( results ) => { - return map( - flatten( results ) - .filter( ( result ) => !! result.id ) - .slice( 0, perPage ), - ( result ) => ( { - id: result.id, - url: result.url, - title: decodeEntities( result.title ) || __( '(no title)' ), - type: result.subtype || result.type, - } ) - ); - } ); -}; - -class EditorProvider extends Component { - constructor( props ) { - super( ...arguments ); - - this.getBlockEditorSettings = memize( this.getBlockEditorSettings, { - maxSize: 1, - } ); - - this.getDefaultBlockContext = memize( this.getDefaultBlockContext, { - maxSize: 1, - } ); - +import usePostContentEditor from './use-post-content-editor'; +import { store as editorStore } from '../../store'; +import useBlockEditorSettings from './use-block-editor-settings'; + +function EditorProvider( { + __unstableTemplate, + post, + settings, + recovery, + initialEdits, + children, +} ) { + const defaultBlockContext = useMemo( () => { + if ( post.type === 'wp_template' ) { + return {}; + } + return { postId: post.id, postType: post.type }; + }, [ post.id, post.type ] ); + const { selectionEnd, selectionStart, isReady } = useSelect( ( select ) => { + const { + getEditorSelectionStart, + getEditorSelectionEnd, + __unstableIsEditorReady, + } = select( editorStore ); + return { + isReady: __unstableIsEditorReady(), + selectionStart: getEditorSelectionStart(), + selectionEnd: getEditorSelectionEnd(), + }; + }, [] ); + const { id, type } = __unstableTemplate ?? post; + const blockEditorProps = usePostContentEditor( type, id ); + const editorSettings = useBlockEditorSettings( + settings, + !! __unstableTemplate + ); + const { + updatePostLock, + setupEditor, + updateEditorSettings, + __experimentalTearDownEditor, + __unstableSetupTemplate, + } = useDispatch( editorStore ); + const { createWarningNotice } = useDispatch( noticesStore ); + + // Iniitialize and tear down the editor. + // Ideally this should be synced on each change and not just something you do once. + useLayoutEffect( () => { // Assume that we don't need to initialize in the case of an error recovery. - if ( props.recovery ) { + if ( recovery ) { return; } - props.updatePostLock( props.settings.postLock ); - props.setupEditor( - props.post, - props.initialEdits, - props.settings.template - ); - - if ( props.settings.autosave ) { - props.createWarningNotice( + updatePostLock( settings.postLock ); + setupEditor( post, initialEdits, settings.template ); + if ( settings.autosave ) { + createWarningNotice( __( 'There is an autosave of this post that is more recent than the version below.' ), @@ -148,217 +82,53 @@ class EditorProvider extends Component { actions: [ { label: __( 'View the autosave' ), - url: props.settings.autosave.editLink, + url: settings.autosave.editLink, }, ], } ); } - } - getBlockEditorSettings( - settings, - reusableBlocks, - hasUploadPermissions, - canUserUseUnfilteredHTML, - undo, - shouldInsertAtTheTop - ) { - return { - ...pick( settings, [ - '__experimentalBlockDirectory', - '__experimentalBlockPatterns', - '__experimentalBlockPatternCategories', - '__experimentalEnableFullSiteEditing', - '__experimentalEnableFullSiteEditingDemo', - '__experimentalFeatures', - '__experimentalGlobalStylesUserEntityId', - '__experimentalGlobalStylesBaseStyles', - '__experimentalGlobalStylesContexts', - '__experimentalPreferredStyleVariations', - '__experimentalSetIsInserterOpened', - 'alignWide', - 'allowedBlockTypes', - 'availableLegacyWidgets', - 'bodyPlaceholder', - 'codeEditingEnabled', - 'colors', - 'disableCustomColors', - 'disableCustomFontSizes', - 'disableCustomGradients', - 'enableCustomUnits', - 'enableCustomLineHeight', - 'focusMode', - 'fontSizes', - 'gradients', - 'hasFixedToolbar', - 'hasReducedUI', - 'imageEditing', - 'imageSizes', - 'imageDimensions', - 'isRTL', - 'keepCaretInsideBlock', - 'maxWidth', - 'onUpdateDefaultBlockStyles', - 'styles', - 'template', - 'templateLock', - 'titlePlaceholder', - ] ), - mediaUpload: hasUploadPermissions ? mediaUpload : undefined, - __experimentalReusableBlocks: reusableBlocks, - __experimentalFetchLinkSuggestions: partialRight( - fetchLinkSuggestions, - settings - ), - __experimentalCanUserUseUnfilteredHTML: canUserUseUnfilteredHTML, - __experimentalUndo: undo, - __experimentalShouldInsertAtTheTop: shouldInsertAtTheTop, + return () => { + __experimentalTearDownEditor(); }; - } - - getDefaultBlockContext( postId, postType ) { - return { postId, postType }; - } + }, [] ); - componentDidMount() { - this.props.updateEditorSettings( this.props.settings ); - } + // Synchronize the editor settings as they change + useEffect( () => { + updateEditorSettings( settings ); + }, [ settings ] ); - componentDidUpdate( prevProps ) { - if ( this.props.settings !== prevProps.settings ) { - this.props.updateEditorSettings( this.props.settings ); + // Synchronize the template as it changes + useEffect( () => { + if ( __unstableTemplate ) { + __unstableSetupTemplate( __unstableTemplate ); } - } + }, [ __unstableTemplate?.id ] ); - componentWillUnmount() { - this.props.tearDownEditor(); + if ( ! isReady ) { + return null; } - render() { - const { - canUserUseUnfilteredHTML, - children, - post, - blocks, - resetEditorBlocks, - selectionStart, - selectionEnd, - isReady, - settings, - reusableBlocks, - resetEditorBlocksWithoutUndoLevel, - hasUploadPermissions, - isPostTitleSelected, - undo, - } = this.props; - - if ( ! isReady ) { - return null; - } - - const editorSettings = this.getBlockEditorSettings( - settings, - reusableBlocks, - hasUploadPermissions, - canUserUseUnfilteredHTML, - undo, - isPostTitleSelected - ); - - const defaultBlockContext = this.getDefaultBlockContext( - post.id, - post.type - ); - - return ( - <> - <EditorStyles styles={ settings.styles } /> - <EntityProvider kind="root" type="site"> - <EntityProvider - kind="postType" - type={ post.type } - id={ post.id } + return ( + <EntityProvider kind="root" type="site"> + <EntityProvider kind="postType" type={ post.type } id={ post.id }> + <BlockContextProvider value={ defaultBlockContext }> + <BlockEditorProvider + { ...blockEditorProps } + selectionStart={ selectionStart } + selectionEnd={ selectionEnd } + settings={ editorSettings } + useSubRegistry={ false } > - <BlockContextProvider value={ defaultBlockContext }> - <BlockEditorProvider - value={ blocks } - onInput={ resetEditorBlocksWithoutUndoLevel } - onChange={ resetEditorBlocks } - selectionStart={ selectionStart } - selectionEnd={ selectionEnd } - settings={ editorSettings } - useSubRegistry={ false } - > - { children } - <ReusableBlocksMenuItems /> - <ConvertToGroupButtons /> - </BlockEditorProvider> - </BlockContextProvider> - </EntityProvider> - </EntityProvider> - </> - ); - } + { children } + <ReusableBlocksMenuItems /> + <ConvertToGroupButtons /> + </BlockEditorProvider> + </BlockContextProvider> + </EntityProvider> + </EntityProvider> + ); } -export default compose( [ - withRegistryProvider, - withSelect( ( select ) => { - const { - canUserUseUnfilteredHTML, - __unstableIsEditorReady: isEditorReady, - getEditorBlocks, - getEditorSelectionStart, - getEditorSelectionEnd, - isPostTitleSelected, - } = select( 'core/editor' ); - const { canUser } = select( 'core' ); - - return { - canUserUseUnfilteredHTML: canUserUseUnfilteredHTML(), - isReady: isEditorReady(), - blocks: getEditorBlocks(), - selectionStart: getEditorSelectionStart(), - selectionEnd: getEditorSelectionEnd(), - reusableBlocks: select( 'core' ).getEntityRecords( - 'postType', - 'wp_block', - { per_page: -1 } - ), - hasUploadPermissions: defaultTo( - canUser( 'create', 'media' ), - true - ), - // This selector is only defined on mobile. - isPostTitleSelected: isPostTitleSelected && isPostTitleSelected(), - }; - } ), - withDispatch( ( dispatch ) => { - const { - setupEditor, - updatePostLock, - resetEditorBlocks, - updateEditorSettings, - __experimentalTearDownEditor, - undo, - } = dispatch( 'core/editor' ); - const { createWarningNotice } = dispatch( 'core/notices' ); - - return { - setupEditor, - updatePostLock, - createWarningNotice, - resetEditorBlocks, - updateEditorSettings, - resetEditorBlocksWithoutUndoLevel( blocks, options ) { - resetEditorBlocks( blocks, { - ...options, - __unstableShouldCreateUndoLevel: false, - } ); - }, - tearDownEditor: __experimentalTearDownEditor, - undo, - }; - } ), -] )( EditorProvider ); +export default withRegistryProvider( EditorProvider ); diff --git a/packages/editor/src/components/provider/index.native.js b/packages/editor/src/components/provider/index.native.js index 786f20c4ec065d..858d772d62c55a 100644 --- a/packages/editor/src/components/provider/index.native.js +++ b/packages/editor/src/components/provider/index.native.js @@ -73,9 +73,14 @@ class NativeEditorProvider extends Component { } componentDidMount() { - const { capabilities } = this.props; + const { capabilities, colors, gradients } = this.props; - this.props.updateSettings( capabilities ); + this.props.updateSettings( { + ...capabilities, + // Set theme colors for the editor + ...( colors ? { colors } : {} ), + ...( gradients ? { gradients } : {} ), + } ); this.subscriptionParentGetHtml = subscribeParentGetHtml( () => { this.serializeToNativeAction(); diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js new file mode 100644 index 00000000000000..9338189d1ecec0 --- /dev/null +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -0,0 +1,208 @@ +/** + * External dependencies + */ +import { map, pick, defaultTo, flatten, partialRight } from 'lodash'; + +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { store as coreStore } from '@wordpress/core-data'; +import apiFetch from '@wordpress/api-fetch'; +import { addQueryArgs } from '@wordpress/url'; +import { decodeEntities } from '@wordpress/html-entities'; + +/** + * Internal dependencies + */ +import { mediaUpload } from '../../utils'; +import { store as editorStore } from '../../store'; + +/** + * Fetches link suggestions from the API. This function is an exact copy of a function found at: + * + * packages/edit-navigation/src/index.js + * + * It seems like there is no suitable package to import this from. Ideally it would be either part of core-data. + * Until we refactor it, just copying the code is the simplest solution. + * + * @param {string} search + * @param {Object} [searchArguments] + * @param {number} [searchArguments.isInitialSuggestions] + * @param {number} [searchArguments.type] + * @param {number} [searchArguments.subtype] + * @param {number} [searchArguments.page] + * @param {Object} [editorSettings] + * @param {boolean} [editorSettings.disablePostFormats=false] + * @return {Promise<Object[]>} List of suggestions + */ + +const fetchLinkSuggestions = async ( + search, + { isInitialSuggestions, type, subtype, page, perPage: perPageArg } = {}, + { disablePostFormats = false } = {} +) => { + const perPage = perPageArg || isInitialSuggestions ? 3 : 20; + + const queries = []; + + if ( ! type || type === 'post' ) { + queries.push( + apiFetch( { + path: addQueryArgs( '/wp/v2/search', { + search, + page, + per_page: perPage, + type: 'post', + subtype, + } ), + } ).catch( () => [] ) // fail by returning no results + ); + } + + if ( ! type || type === 'term' ) { + queries.push( + apiFetch( { + path: addQueryArgs( '/wp/v2/search', { + search, + page, + per_page: perPage, + type: 'term', + subtype, + } ), + } ).catch( () => [] ) + ); + } + + if ( ! disablePostFormats && ( ! type || type === 'post-format' ) ) { + queries.push( + apiFetch( { + path: addQueryArgs( '/wp/v2/search', { + search, + page, + per_page: perPage, + type: 'post-format', + subtype, + } ), + } ).catch( () => [] ) + ); + } + + return Promise.all( queries ).then( ( results ) => { + return map( + flatten( results ) + .filter( ( result ) => !! result.id ) + .slice( 0, perPage ), + ( result ) => ( { + id: result.id, + url: result.url, + title: decodeEntities( result.title ) || __( '(no title)' ), + type: result.subtype || result.type, + } ) + ); + } ); +}; + +/** + * React hook used to compute the block editor settings to use for the post editor. + * + * @param {Object} settings EditorProvider settings prop. + * @param {boolean} hasTemplate Whether template mode is enabled. + * + * @return {Object} Block Editor Settings. + */ +function useBlockEditorSettings( settings, hasTemplate ) { + const { + reusableBlocks, + hasUploadPermissions, + canUseUnfilteredHTML, + isTitleSelected, + } = useSelect( ( select ) => { + const { canUserUseUnfilteredHTML, isPostTitleSelected } = select( + editorStore + ); + const { canUser } = select( coreStore ); + + return { + canUseUnfilteredHTML: canUserUseUnfilteredHTML(), + reusableBlocks: select( 'core' ).getEntityRecords( + 'postType', + 'wp_block', + { per_page: -1 } + ), + hasUploadPermissions: defaultTo( + canUser( 'create', 'media' ), + true + ), + // This selector is only defined on mobile. + isTitleSelected: isPostTitleSelected && isPostTitleSelected(), + }; + }, [] ); + + const { undo } = useDispatch( editorStore ); + + return useMemo( + () => ( { + ...pick( settings, [ + '__experimentalBlockDirectory', + '__experimentalBlockPatterns', + '__experimentalBlockPatternCategories', + '__experimentalFeatures', + '__experimentalGlobalStylesUserEntityId', + '__experimentalGlobalStylesBaseStyles', + '__experimentalPreferredStyleVariations', + '__experimentalSetIsInserterOpened', + 'alignWide', + 'allowedBlockTypes', + 'availableLegacyWidgets', + 'bodyPlaceholder', + 'codeEditingEnabled', + 'colors', + 'disableCustomColors', + 'disableCustomFontSizes', + 'disableCustomGradients', + 'enableCustomUnits', + 'enableCustomLineHeight', + 'focusMode', + 'fontSizes', + 'gradients', + 'hasFixedToolbar', + 'hasReducedUI', + 'imageEditing', + 'imageSizes', + 'imageDimensions', + 'isRTL', + 'keepCaretInsideBlock', + 'maxWidth', + 'onUpdateDefaultBlockStyles', + 'styles', + 'template', + 'templateLock', + 'titlePlaceholder', + ] ), + mediaUpload: hasUploadPermissions ? mediaUpload : undefined, + __experimentalReusableBlocks: reusableBlocks, + __experimentalFetchLinkSuggestions: partialRight( + fetchLinkSuggestions, + settings + ), + __experimentalCanUserUseUnfilteredHTML: canUseUnfilteredHTML, + __experimentalUndo: undo, + __experimentalShouldInsertAtTheTop: isTitleSelected, + outlineMode: hasTemplate, + } ), + [ + settings, + hasUploadPermissions, + reusableBlocks, + canUseUnfilteredHTML, + undo, + isTitleSelected, + hasTemplate, + ] + ); +} + +export default useBlockEditorSettings; diff --git a/packages/editor/src/components/provider/use-post-content-editor.js b/packages/editor/src/components/provider/use-post-content-editor.js new file mode 100644 index 00000000000000..d220803eb287e8 --- /dev/null +++ b/packages/editor/src/components/provider/use-post-content-editor.js @@ -0,0 +1,77 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; +import { useCallback } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import serializeBlocks from '../../store/utils/serialize-blocks'; + +/** + * This hook provides the required props to edit the "content" of a given postType and postId. + * + * @param {string} postType Post Type. + * @param {string} postId Post Id. + * + * @return {Object} BlockEditorProvider props. + */ +function usePostContentEditor( postType, postId ) { + const blocks = useSelect( + ( select ) => { + const { getEditedEntityRecord } = select( coreStore ); + return getEditedEntityRecord( 'postType', postType, postId ).blocks; + }, + [ postType, postId ] + ); + const { __unstableCreateUndoLevel, editEntityRecord } = useDispatch( + coreStore + ); + + const onChange = useCallback( + ( newBlocks, options ) => { + const { + __unstableShouldCreateUndoLevel, + selectionStart, + selectionEnd, + } = options; + const edits = { blocks: newBlocks, selectionStart, selectionEnd }; + + if ( __unstableShouldCreateUndoLevel !== false ) { + const noChange = blocks === edits.blocks; + if ( noChange ) { + return __unstableCreateUndoLevel( + 'postType', + postType, + postId + ); + } + + // We create a new function here on every persistent edit + // to make sure the edit makes the post dirty and creates + // a new undo level. + edits.content = ( { blocks: blocksForSerialization = [] } ) => + serializeBlocks( blocksForSerialization ); + } + + editEntityRecord( 'postType', postType, postId, edits ); + }, + [ blocks, postId, postType ] + ); + + const onInput = useCallback( + ( newBlocks, options ) => { + onChange( newBlocks, { + ...options, + __unstableShouldCreateUndoLevel: false, + } ); + }, + [ onChange ] + ); + + return { value: blocks, onChange, onInput }; +} + +export default usePostContentEditor; diff --git a/packages/editor/src/editor-styles.scss b/packages/editor/src/editor-styles.scss index 907fe25dec0e39..1921e7388ddd7d 100644 --- a/packages/editor/src/editor-styles.scss +++ b/packages/editor/src/editor-styles.scss @@ -127,7 +127,7 @@ ol ul { } &.wp-align-wide { - max-width: 1100px; + max-width: $content-width; } } diff --git a/packages/editor/src/hooks/custom-sources-backwards-compatibility.js b/packages/editor/src/hooks/custom-sources-backwards-compatibility.js index 51d5bf4ac5fda0..a32597790eb123 100644 --- a/packages/editor/src/hooks/custom-sources-backwards-compatibility.js +++ b/packages/editor/src/hooks/custom-sources-backwards-compatibility.js @@ -6,6 +6,7 @@ import { pickBy, mapValues, isEmpty, mapKeys } from 'lodash'; /** * WordPress dependencies */ +import { store as blocksStore } from '@wordpress/blocks'; import { select as globalSelect, useSelect } from '@wordpress/data'; import { useEntityProp } from '@wordpress/core-data'; import { useMemo } from '@wordpress/element'; @@ -135,7 +136,7 @@ addFilter( // // In the future, we could support updating block settings, at which point this // implementation could use that mechanism instead. -globalSelect( 'core/blocks' ) +globalSelect( blocksStore ) .getBlockTypes() - .map( ( { name } ) => globalSelect( 'core/blocks' ).getBlockType( name ) ) + .map( ( { name } ) => globalSelect( blocksStore ).getBlockType( name ) ) .forEach( shimAttributeSource ); diff --git a/packages/editor/src/index.js b/packages/editor/src/index.js index 5e119148adb1de..d84cb1fcf21b4d 100644 --- a/packages/editor/src/index.js +++ b/packages/editor/src/index.js @@ -2,23 +2,17 @@ * WordPress dependencies */ import '@wordpress/block-editor'; -import '@wordpress/blocks'; import '@wordpress/core-data'; -import '@wordpress/keyboard-shortcuts'; -import '@wordpress/notices'; -import '@wordpress/reusable-blocks'; import '@wordpress/rich-text'; -import '@wordpress/viewport'; /** * Internal dependencies */ -import './store'; import './hooks'; +export { storeConfig, store } from './store'; export * from './components'; export * from './utils'; -export { storeConfig } from './store'; /* * Backward compatibility diff --git a/packages/editor/src/index.native.js b/packages/editor/src/index.native.js index 97a922ba6423e9..35fcddf0b80030 100644 --- a/packages/editor/src/index.native.js +++ b/packages/editor/src/index.native.js @@ -2,15 +2,14 @@ * WordPress dependencies */ import '@wordpress/block-editor'; -import '@wordpress/blocks'; import '@wordpress/core-data'; import '@wordpress/rich-text'; /** * Internal dependencies */ -import './store'; import './hooks'; +export { store } from './store'; export * from './components'; export * from './utils'; diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index ee943031a5eea9..19a4d89d0ee335 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -7,19 +7,16 @@ import { has } from 'lodash'; * WordPress dependencies */ import deprecated from '@wordpress/deprecated'; -import { - dispatch, - select, - syncSelect, - apiFetch, -} from '@wordpress/data-controls'; +import { controls } from '@wordpress/data'; +import { apiFetch } from '@wordpress/data-controls'; import { parse, synchronizeBlocksWithTemplate } from '@wordpress/blocks'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies */ import { - STORE_KEY, + STORE_NAME, POST_UPDATE_TRANSACTION_ID, TRASH_POST_NOTICE_ID, } from './constants'; @@ -80,6 +77,27 @@ export function* setupEditor( post, edits, template ) { } } +/** + * Initiliazes an FSE template into the core-data store. + * We could avoid this action entirely by having a fallback if the edit is undefined. + * + * @param {Object} template Template object. + */ +export function* __unstableSetupTemplate( template ) { + const blocks = parse( template.content.raw ); + yield controls.dispatch( + 'core', + 'editEntityRecord', + 'postType', + template.type, + template.id, + { + blocks, + }, + { __unstableShouldCreateUndoLevel: false } + ); +} + /** * Returns an action object signalling that the editor is being destroyed and * that any necessary state or side-effect cleanup should occur. @@ -122,8 +140,8 @@ export function* resetAutosave( newAutosave ) { plugin: 'Gutenberg', } ); - const postId = yield select( STORE_KEY, 'getCurrentPostId' ); - yield dispatch( 'core', 'receiveAutosaves', postId, newAutosave ); + const postId = yield controls.select( STORE_NAME, 'getCurrentPostId' ); + yield controls.dispatch( 'core', 'receiveAutosaves', postId, newAutosave ); return { type: '__INERT__' }; } @@ -196,8 +214,8 @@ export function setupEditorState( post ) { * @yield {Object} Action object or control. */ export function* editPost( edits, options ) { - const { id, type } = yield select( STORE_KEY, 'getCurrentPost' ); - yield dispatch( + const { id, type } = yield controls.select( STORE_NAME, 'getCurrentPost' ); + yield controls.dispatch( 'core', 'editEntityRecord', 'postType', @@ -229,21 +247,26 @@ export function __experimentalOptimisticUpdatePost( edits ) { * @param {Object} options */ export function* savePost( options = {} ) { - if ( ! ( yield select( STORE_KEY, 'isEditedPostSaveable' ) ) ) { + if ( ! ( yield controls.select( STORE_NAME, 'isEditedPostSaveable' ) ) ) { return; } let edits = { - content: yield select( STORE_KEY, 'getEditedPostContent' ), + content: yield controls.select( STORE_NAME, 'getEditedPostContent' ), }; if ( ! options.isAutosave ) { - yield dispatch( STORE_KEY, 'editPost', edits, { undoIgnore: true } ); + yield controls.dispatch( STORE_NAME, 'editPost', edits, { + undoIgnore: true, + } ); } yield __experimentalRequestPostUpdateStart( options ); - const previousRecord = yield select( STORE_KEY, 'getCurrentPost' ); + const previousRecord = yield controls.select( + STORE_NAME, + 'getCurrentPost' + ); edits = { id: previousRecord.id, - ...( yield select( + ...( yield controls.select( 'core', 'getEntityRecordNonTransientEdits', 'postType', @@ -252,7 +275,7 @@ export function* savePost( options = {} ) { ) ), ...edits, }; - yield dispatch( + yield controls.dispatch( 'core', 'saveEntityRecord', 'postType', @@ -262,7 +285,7 @@ export function* savePost( options = {} ) { ); yield __experimentalRequestPostUpdateFinish( options ); - const error = yield select( + const error = yield controls.select( 'core', 'getLastEntitySaveError', 'postType', @@ -276,23 +299,38 @@ export function* savePost( options = {} ) { error, } ); if ( args.length ) { - yield dispatch( 'core/notices', 'createErrorNotice', ...args ); + yield controls.dispatch( + noticesStore, + 'createErrorNotice', + ...args + ); } } else { - const updatedRecord = yield select( STORE_KEY, 'getCurrentPost' ); + const updatedRecord = yield controls.select( + STORE_NAME, + 'getCurrentPost' + ); const args = getNotificationArgumentsForSaveSuccess( { previousPost: previousRecord, post: updatedRecord, - postType: yield select( 'core', 'getPostType', updatedRecord.type ), + postType: yield controls.resolveSelect( + 'core', + 'getPostType', + updatedRecord.type + ), options, } ); if ( args.length ) { - yield dispatch( 'core/notices', 'createSuccessNotice', ...args ); + yield controls.dispatch( + noticesStore, + 'createSuccessNotice', + ...args + ); } // Make sure that any edits after saving create an undo level and are // considered for change detection. if ( ! options.isAutosave ) { - yield dispatch( + yield controls.dispatch( 'core/block-editor', '__unstableMarkLastChangeAsPersistent' ); @@ -304,9 +342,16 @@ export function* savePost( options = {} ) { * Action generator for handling refreshing the current post. */ export function* refreshPost() { - const post = yield select( STORE_KEY, 'getCurrentPost' ); - const postTypeSlug = yield select( STORE_KEY, 'getCurrentPostType' ); - const postType = yield select( 'core', 'getPostType', postTypeSlug ); + const post = yield controls.select( STORE_NAME, 'getCurrentPost' ); + const postTypeSlug = yield controls.select( + STORE_NAME, + 'getCurrentPostType' + ); + const postType = yield controls.resolveSelect( + 'core', + 'getPostType', + postTypeSlug + ); const newPost = yield apiFetch( { // Timestamp arg allows caller to bypass browser caching, which is // expected for this specific function. @@ -314,27 +359,38 @@ export function* refreshPost() { `/wp/v2/${ postType.rest_base }/${ post.id }` + `?context=edit&_timestamp=${ Date.now() }`, } ); - yield dispatch( STORE_KEY, 'resetPost', newPost ); + yield controls.dispatch( STORE_NAME, 'resetPost', newPost ); } /** * Action generator for trashing the current post in the editor. */ export function* trashPost() { - const postTypeSlug = yield select( STORE_KEY, 'getCurrentPostType' ); - const postType = yield select( 'core', 'getPostType', postTypeSlug ); - yield dispatch( 'core/notices', 'removeNotice', TRASH_POST_NOTICE_ID ); + const postTypeSlug = yield controls.select( + STORE_NAME, + 'getCurrentPostType' + ); + const postType = yield controls.resolveSelect( + 'core', + 'getPostType', + postTypeSlug + ); + yield controls.dispatch( + noticesStore, + 'removeNotice', + TRASH_POST_NOTICE_ID + ); try { - const post = yield select( STORE_KEY, 'getCurrentPost' ); + const post = yield controls.select( STORE_NAME, 'getCurrentPost' ); yield apiFetch( { path: `/wp/v2/${ postType.rest_base }/${ post.id }`, method: 'DELETE', } ); - yield dispatch( STORE_KEY, 'savePost' ); + yield controls.dispatch( STORE_NAME, 'savePost' ); } catch ( error ) { - yield dispatch( - 'core/notices', + yield controls.dispatch( + noticesStore, 'createErrorNotice', ...getNotificationArgumentsForTrashFail( { error } ) ); @@ -351,20 +407,23 @@ export function* trashPost() { */ export function* autosave( { local = false, ...options } = {} ) { if ( local ) { - const post = yield select( STORE_KEY, 'getCurrentPost' ); - const isPostNew = yield select( STORE_KEY, 'isEditedPostNew' ); - const title = yield select( - STORE_KEY, + const post = yield controls.select( STORE_NAME, 'getCurrentPost' ); + const isPostNew = yield controls.select( + STORE_NAME, + 'isEditedPostNew' + ); + const title = yield controls.select( + STORE_NAME, 'getEditedPostAttribute', 'title' ); - const content = yield select( - STORE_KEY, + const content = yield controls.select( + STORE_NAME, 'getEditedPostAttribute', 'content' ); - const excerpt = yield select( - STORE_KEY, + const excerpt = yield controls.select( + STORE_NAME, 'getEditedPostAttribute', 'excerpt' ); @@ -377,7 +436,7 @@ export function* autosave( { local = false, ...options } = {} ) { excerpt, }; } else { - yield dispatch( STORE_KEY, 'savePost', { + yield controls.dispatch( STORE_NAME, 'savePost', { isAutosave: true, ...options, } ); @@ -391,7 +450,7 @@ export function* autosave( { local = false, ...options } = {} ) { * @yield {Object} Action object. */ export function* redo() { - yield dispatch( 'core', 'redo' ); + yield controls.dispatch( 'core', 'redo' ); } /** @@ -400,7 +459,7 @@ export function* redo() { * @yield {Object} Action object. */ export function* undo() { - yield dispatch( 'core', 'undo' ); + yield controls.dispatch( 'core', 'undo' ); } /** @@ -578,9 +637,12 @@ export function* resetEditorBlocks( blocks, options = {} ) { const edits = { blocks, selectionStart, selectionEnd }; if ( __unstableShouldCreateUndoLevel !== false ) { - const { id, type } = yield select( STORE_KEY, 'getCurrentPost' ); + const { id, type } = yield controls.select( + STORE_NAME, + 'getCurrentPost' + ); const noChange = - ( yield syncSelect( + ( yield controls.select( 'core', 'getEditedEntityRecord', 'postType', @@ -588,7 +650,7 @@ export function* resetEditorBlocks( blocks, options = {} ) { id ) ).blocks === edits.blocks; if ( noChange ) { - return yield dispatch( + return yield controls.dispatch( 'core', '__unstableCreateUndoLevel', 'postType', @@ -630,7 +692,7 @@ const getBlockEditorAction = ( name ) => alternative: "`wp.data.dispatch( 'core/block-editor' )." + name + '`', } ); - yield dispatch( 'core/block-editor', name, ...args ); + yield controls.dispatch( 'core/block-editor', name, ...args ); }; /** diff --git a/packages/editor/src/store/constants.js b/packages/editor/src/store/constants.js index 1946c19d75b80e..073efe20455410 100644 --- a/packages/editor/src/store/constants.js +++ b/packages/editor/src/store/constants.js @@ -11,7 +11,7 @@ export const EDIT_MERGE_PROPERTIES = new Set( [ 'meta' ] ); * * @type {string} */ -export const STORE_KEY = 'core/editor'; +export const STORE_NAME = 'core/editor'; export const POST_UPDATE_TRANSACTION_ID = 'post-update'; export const SAVE_POST_NOTICE_ID = 'SAVE_POST_NOTICE_ID'; diff --git a/packages/editor/src/store/index.js b/packages/editor/src/store/index.js index af10a60cf74369..9d4a0d6ad71881 100644 --- a/packages/editor/src/store/index.js +++ b/packages/editor/src/store/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { registerStore } from '@wordpress/data'; +import { createReduxStore, registerStore } from '@wordpress/data'; import { controls as dataControls } from '@wordpress/data-controls'; /** @@ -11,7 +11,7 @@ import reducer from './reducer'; import * as selectors from './selectors'; import * as actions from './actions'; import controls from './controls'; -import { STORE_KEY } from './constants'; +import { STORE_NAME } from './constants'; /** * Post editor data store configuration. @@ -30,9 +30,21 @@ export const storeConfig = { }, }; -const store = registerStore( STORE_KEY, { +/** + * Store definition for the editor namespace. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore + * + * @type {Object} + */ +export const store = createReduxStore( STORE_NAME, { ...storeConfig, persist: [ 'preferences' ], } ); -export default store; +// Once we build a more generic persistence plugin that works across types of stores +// we'd be able to replace this with a register call. +registerStore( STORE_NAME, { + ...storeConfig, + persist: [ 'preferences' ], +} ); diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index 764c3c63ba0e06..5849de6a6521fa 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -1,7 +1,17 @@ /** * External dependencies */ -import { find, get, has, pick, mapValues, includes, some } from 'lodash'; +import { + find, + get, + has, + isString, + pick, + mapValues, + includes, + some, +} from 'lodash'; +import createSelector from 'rememo'; /** * WordPress dependencies @@ -144,12 +154,6 @@ export const isEditedPostDirty = createRegistrySelector( */ export const hasNonPostEntityChanges = createRegistrySelector( ( select ) => ( state ) => { - const enableFullSiteEditing = getEditorSettings( state ) - .__experimentalEnableFullSiteEditing; - if ( ! enableFullSiteEditing ) { - return false; - } - const dirtyEntityRecords = select( 'core' ).__experimentalGetDirtyEntityRecords(); @@ -1265,6 +1269,20 @@ export function getEditorBlocks( state ) { return getEditedPostAttribute( state, 'blocks' ) || EMPTY_ARRAY; } +/** + * Checks whether a post is an auto-draft ignoring the optimistic transaction. + * This selector shouldn't be necessary. It's currently used as a workaround + * to avoid template resolution for auto-drafts which has a backend bug. + * + * @param {Object} state State. + * @return {boolean} Whether the post is "auto-draft" on the backend. + */ +export function __unstableIsAutodraftPost( state ) { + const post = getCurrentPost( state ); + const isSaving = isSavingPost( state ); + return isSaving || post.status === 'auto-draft'; +} + /** * A block selection object. * @@ -1656,3 +1674,59 @@ export const hasInserterItems = getBlockEditorSelector( 'hasInserterItems' ); export const getBlockListSettings = getBlockEditorSelector( 'getBlockListSettings' ); + +/** + * Returns the default template types. + * + * @param {Object} state Global application state. + * + * @return {Object} The template types. + */ +export function __experimentalGetDefaultTemplateTypes( state ) { + return getEditorSettings( state )?.defaultTemplateTypes; +} + +/** + * Returns a default template type searched by slug. + * + * @param {Object} state Global application state. + * @param {string} slug The template type slug. + * + * @return {Object} The template type. + */ +export const __experimentalGetDefaultTemplateType = createSelector( + ( state, slug ) => + find( __experimentalGetDefaultTemplateTypes( state ), { slug } ) || {}, + ( state, slug ) => [ __experimentalGetDefaultTemplateTypes( state ), slug ] +); + +/** + * Given a template entity, return information about it which is ready to be + * rendered, such as the title and description. + * + * @param {Object} state Global application state. + * @param {Object} template The template for which we need information. + * @return {Object} Information about the template, including title and description. + */ +export function __experimentalGetTemplateInfo( state, template ) { + if ( ! template ) { + return {}; + } + + const { excerpt, slug, title } = template; + const { + title: defaultTitle, + description: defaultDescription, + } = __experimentalGetDefaultTemplateType( state, slug ); + + const templateTitle = isString( title ) ? title : title?.rendered; + const templateDescription = isString( excerpt ) ? excerpt : excerpt?.raw; + + return { + title: + templateTitle && templateTitle !== slug + ? templateTitle + : defaultTitle || slug, + description: templateDescription || defaultDescription, + }; +} diff --git a/packages/editor/src/store/test/actions.js b/packages/editor/src/store/test/actions.js index 686fd067a47e95..82d94c7dd94ff7 100644 --- a/packages/editor/src/store/test/actions.js +++ b/packages/editor/src/store/test/actions.js @@ -1,51 +1,20 @@ /** * WordPress dependencies */ -import { select, dispatch, apiFetch } from '@wordpress/data-controls'; +import { apiFetch } from '@wordpress/data-controls'; +import { controls } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies */ import * as actions from '../actions'; import { - STORE_KEY, + STORE_NAME, TRASH_POST_NOTICE_ID, POST_UPDATE_TRANSACTION_ID, } from '../constants'; -jest.mock( '@wordpress/data-controls' ); - -select.mockImplementation( ( ...args ) => { - const { select: actualSelect } = jest.requireActual( - '@wordpress/data-controls' - ); - return actualSelect( ...args ); -} ); - -dispatch.mockImplementation( ( ...args ) => { - const { dispatch: actualDispatch } = jest.requireActual( - '@wordpress/data-controls' - ); - return actualDispatch( ...args ); -} ); - -const apiFetchThrowError = ( error ) => { - apiFetch.mockClear(); - apiFetch.mockImplementation( () => { - throw error; - } ); -}; - -const apiFetchDoActual = () => { - apiFetch.mockClear(); - apiFetch.mockImplementation( ( ...args ) => { - const { apiFetch: fetch } = jest.requireActual( - '@wordpress/data-controls' - ); - return fetch( ...args ); - } ); -}; - const postType = { rest_base: 'posts', labels: { @@ -79,7 +48,7 @@ describe( 'Post generator actions', () => { reset( isAutosave ); const { value } = fulfillment.next(); expect( value ).toEqual( - select( STORE_KEY, 'isEditedPostSaveable' ) + controls.select( STORE_NAME, 'isEditedPostSaveable' ) ); }, ], @@ -89,7 +58,7 @@ describe( 'Post generator actions', () => { () => { const { value } = fulfillment.next( true ); expect( value ).toEqual( - select( STORE_KEY, 'getEditedPostContent' ) + controls.select( STORE_NAME, 'getEditedPostContent' ) ); }, ], @@ -101,7 +70,7 @@ describe( 'Post generator actions', () => { const edits = { content: currentPost().content }; const { value } = fulfillment.next( edits.content ); expect( value ).toEqual( - dispatch( STORE_KEY, 'editPost', edits, { + controls.dispatch( STORE_NAME, 'editPost', edits, { undoIgnore: true, } ) ); @@ -125,7 +94,7 @@ describe( 'Post generator actions', () => { () => { const { value } = fulfillment.next(); expect( value ).toEqual( - select( STORE_KEY, 'getCurrentPost' ) + controls.select( STORE_NAME, 'getCurrentPost' ) ); }, ], @@ -136,7 +105,7 @@ describe( 'Post generator actions', () => { const post = currentPost(); const { value } = fulfillment.next( post ); expect( value ).toEqual( - select( + controls.select( 'core', 'getEntityRecordNonTransientEdits', 'postType', @@ -153,7 +122,7 @@ describe( 'Post generator actions', () => { const post = currentPost(); const { value } = fulfillment.next( post ); expect( value ).toEqual( - dispatch( + controls.dispatch( 'core', 'saveEntityRecord', 'postType', @@ -184,7 +153,7 @@ describe( 'Post generator actions', () => { const post = currentPost(); const { value } = fulfillment.next(); expect( value ).toEqual( - select( + controls.select( 'core', 'getLastEntitySaveError', 'postType', @@ -200,7 +169,7 @@ describe( 'Post generator actions', () => { () => { const { value } = fulfillment.next(); expect( value ).toEqual( - select( STORE_KEY, 'getCurrentPost' ) + controls.select( STORE_NAME, 'getCurrentPost' ) ); }, ], @@ -211,7 +180,11 @@ describe( 'Post generator actions', () => { const post = currentPost(); const { value } = fulfillment.next( post ); expect( value ).toEqual( - select( 'core', 'getPostType', post.type ) + controls.resolveSelect( + 'core', + 'getPostType', + post.type + ) ); }, ], @@ -222,8 +195,8 @@ describe( 'Post generator actions', () => { if ( ! isAutosave && currentPostStatus === 'publish' ) { const { value } = fulfillment.next( postType ); expect( value ).toEqual( - dispatch( - 'core/notices', + controls.dispatch( + noticesStore, 'createSuccessNotice', 'Updated Post', { @@ -243,7 +216,7 @@ describe( 'Post generator actions', () => { if ( ! isAutosave ) { const { value } = fulfillment.next(); expect( value ).toEqual( - dispatch( + controls.dispatch( 'core/block-editor', '__unstableMarkLastChangeAsPersistent' ) @@ -316,18 +289,19 @@ describe( 'Post generator actions', () => { fulfillment.next( postTypeSlug ); fulfillment.next( postType ); fulfillment.next(); + fulfillment.next( currentPost ); }; it( 'yields expected action for selecting the current post type slug', () => { reset(); const { value } = fulfillment.next(); expect( value ).toEqual( - select( STORE_KEY, 'getCurrentPostType' ) + controls.select( STORE_NAME, 'getCurrentPostType' ) ); } ); it( 'yields expected action for selecting the post type object', () => { const { value } = fulfillment.next( postTypeSlug ); expect( value ).toEqual( - select( 'core', 'getPostType', postTypeSlug ) + controls.resolveSelect( 'core', 'getPostType', postTypeSlug ) ); } ); it( @@ -336,8 +310,8 @@ describe( 'Post generator actions', () => { () => { const { value } = fulfillment.next( postType ); expect( value ).toEqual( - dispatch( - 'core/notices', + controls.dispatch( + noticesStore, 'removeNotice', TRASH_POST_NOTICE_ID ) @@ -346,16 +320,26 @@ describe( 'Post generator actions', () => { ); it( 'yields expected action for selecting the currentPost', () => { const { value } = fulfillment.next(); - expect( value ).toEqual( select( STORE_KEY, 'getCurrentPost' ) ); + expect( value ).toEqual( + controls.select( STORE_NAME, 'getCurrentPost' ) + ); + } ); + it( 'yields expected action object for the api fetch', () => { + const { value } = fulfillment.next( currentPost ); + expect( value ).toEqual( + apiFetch( { + path: `/wp/v2/${ postType.rest_base }/${ currentPost.id }`, + method: 'DELETE', + } ) + ); } ); describe( 'expected yields when fetch throws an error', () => { it( 'yields expected action for dispatching an error notice', () => { const error = { foo: 'bar', code: 'fail' }; - apiFetchThrowError( error ); - const { value } = fulfillment.next( currentPost ); + const { value } = fulfillment.throw( error ); expect( value ).toEqual( - dispatch( - 'core/notices', + controls.dispatch( + noticesStore, 'createErrorNotice', 'Trashing failed', { @@ -366,21 +350,13 @@ describe( 'Post generator actions', () => { } ); } ); describe( 'expected yields when fetch does not throw an error', () => { - it( 'yields expected action object for the api fetch', () => { - apiFetchDoActual(); + it( 'yields expected dispatch action for saving the post', () => { rewind(); - const { value } = fulfillment.next( currentPost ); + const { value } = fulfillment.next(); expect( value ).toEqual( - apiFetch( { - path: `/wp/v2/${ postType.rest_base }/${ currentPost.id }`, - method: 'DELETE', - } ) + controls.dispatch( STORE_NAME, 'savePost' ) ); } ); - it( 'yields expected dispatch action for saving the post', () => { - const { value } = fulfillment.next(); - expect( value ).toEqual( dispatch( STORE_KEY, 'savePost' ) ); - } ); } ); } ); describe( 'refreshPost()', () => { @@ -390,23 +366,24 @@ describe( 'Post generator actions', () => { it( 'yields expected action for selecting the currentPost', () => { reset(); const { value } = fulfillment.next(); - expect( value ).toEqual( select( STORE_KEY, 'getCurrentPost' ) ); + expect( value ).toEqual( + controls.select( STORE_NAME, 'getCurrentPost' ) + ); } ); it( 'yields expected action for selecting the current post type', () => { const { value } = fulfillment.next( currentPost ); expect( value ).toEqual( - select( STORE_KEY, 'getCurrentPostType' ) + controls.select( STORE_NAME, 'getCurrentPostType' ) ); } ); it( 'yields expected action for selecting the post type object', () => { const { value } = fulfillment.next( postTypeSlug ); expect( value ).toEqual( - select( 'core', 'getPostType', postTypeSlug ) + controls.resolveSelect( 'core', 'getPostType', postTypeSlug ) ); } ); it( 'yields expected action for the api fetch call', () => { const { value } = fulfillment.next( postType ); - apiFetchDoActual(); // since the timestamp is a computed value we can't do a direct comparison. // so we'll just see if the path has most of the value. expect( value.request.path ).toEqual( @@ -418,7 +395,7 @@ describe( 'Post generator actions', () => { it( 'yields expected action for dispatching the reset of the post', () => { const { value } = fulfillment.next( currentPost ); expect( value ).toEqual( - dispatch( STORE_KEY, 'resetPost', currentPost ) + controls.dispatch( STORE_NAME, 'resetPost', currentPost ) ); } ); } ); @@ -499,12 +476,12 @@ describe( 'Editor actions', () => { const fulfillment = actions.editPost( edits ); expect( fulfillment.next() ).toEqual( { done: false, - value: select( STORE_KEY, 'getCurrentPost' ), + value: controls.select( STORE_NAME, 'getCurrentPost' ), } ); const post = { id: 1, type: 'post' }; expect( fulfillment.next( post ) ).toEqual( { done: false, - value: dispatch( + value: controls.dispatch( 'core', 'editEntityRecord', 'postType', @@ -538,7 +515,7 @@ describe( 'Editor actions', () => { const fulfillment = actions.redo(); expect( fulfillment.next() ).toEqual( { done: false, - value: dispatch( 'core', 'redo' ), + value: controls.dispatch( 'core', 'redo' ), } ); expect( fulfillment.next() ).toEqual( { done: true, @@ -552,7 +529,7 @@ describe( 'Editor actions', () => { const fulfillment = actions.undo(); expect( fulfillment.next() ).toEqual( { done: false, - value: dispatch( 'core', 'undo' ), + value: controls.dispatch( 'core', 'undo' ), } ); expect( fulfillment.next() ).toEqual( { done: true, diff --git a/packages/editor/src/store/test/selectors.js b/packages/editor/src/store/test/selectors.js index 4d6bf5f9a98dc1..fd4a84ecbdbdc6 100644 --- a/packages/editor/src/store/test/selectors.js +++ b/packages/editor/src/store/test/selectors.js @@ -171,8 +171,24 @@ const { isPostSavingLocked, isPostAutosavingLocked, canUserUseUnfilteredHTML, + __experimentalGetDefaultTemplateType, + __experimentalGetDefaultTemplateTypes, + __experimentalGetTemplateInfo, } = selectors; +const defaultTemplateTypes = [ + { + title: 'Default (Index)', + description: 'Main template', + slug: 'index', + }, + { + title: '404 (Not Found)', + description: 'Applied when content cannot be found', + slug: '404', + }, +]; + describe( 'selectors', () => { let cachedSelectors; @@ -452,26 +468,9 @@ describe( 'selectors', () => { } ); describe( 'hasNonPostEntityChanges', () => { - it( 'should return false if the full site editing experiment is disabled.', () => { - const state = { - currentPost: { id: 1, type: 'post' }, - editorSettings: { - __experimentalEnableFullSiteEditing: false, - }, - __experimentalGetDirtyEntityRecords() { - return [ - { kind: 'someKind', name: 'someName', key: 'someKey' }, - ]; - }, - }; - expect( hasNonPostEntityChanges( state ) ).toBe( false ); - } ); it( 'should return true if there are changes to an arbitrary entity', () => { const state = { currentPost: { id: 1, type: 'post' }, - editorSettings: { - __experimentalEnableFullSiteEditing: true, - }, __experimentalGetDirtyEntityRecords() { return [ { kind: 'someKind', name: 'someName', key: 'someKey' }, @@ -483,9 +482,6 @@ describe( 'selectors', () => { it( 'should return false if there are only changes for the current post', () => { const state = { currentPost: { id: 1, type: 'post' }, - editorSettings: { - __experimentalEnableFullSiteEditing: true, - }, __experimentalGetDirtyEntityRecords() { return [ { kind: 'postType', name: 'post', key: 1 } ]; }, @@ -495,9 +491,6 @@ describe( 'selectors', () => { it( 'should return true if there are changes to multiple posts', () => { const state = { currentPost: { id: 1, type: 'post' }, - editorSettings: { - __experimentalEnableFullSiteEditing: true, - }, __experimentalGetDirtyEntityRecords() { return [ { kind: 'postType', name: 'post', key: 1 }, @@ -510,9 +503,6 @@ describe( 'selectors', () => { it( 'should return true if there are changes to multiple posts of different post types', () => { const state = { currentPost: { id: 1, type: 'post' }, - editorSettings: { - __experimentalEnableFullSiteEditing: true, - }, __experimentalGetDirtyEntityRecords() { return [ { kind: 'postType', name: 'post', key: 1 }, @@ -2938,4 +2928,153 @@ describe( 'selectors', () => { expect( canUserUseUnfilteredHTML( state ) ).toBe( false ); } ); } ); + + describe( '__experimentalGetDefaultTemplateTypes', () => { + const state = { editorSettings: { defaultTemplateTypes } }; + + it( 'returns undefined if there are no default template types', () => { + const emptyState = { editorSettings: {} }; + expect( + __experimentalGetDefaultTemplateTypes( emptyState ) + ).toBeUndefined(); + } ); + + it( 'returns a list of default template types if present in state', () => { + expect( + __experimentalGetDefaultTemplateTypes( state ) + ).toHaveLength( 2 ); + } ); + } ); + + describe( '__experimentalGetDefaultTemplateType', () => { + const state = { editorSettings: { defaultTemplateTypes } }; + + it( 'returns an empty object if there are no default template types', () => { + const emptyState = { editorSettings: {} }; + expect( + __experimentalGetDefaultTemplateType( emptyState, 'slug' ) + ).toEqual( {} ); + } ); + + it( 'returns an empty object if the requested slug is not found', () => { + expect( + __experimentalGetDefaultTemplateType( state, 'foobar' ) + ).toEqual( {} ); + } ); + + it( 'returns the requested default template type ', () => { + expect( + __experimentalGetDefaultTemplateType( state, 'index' ) + ).toEqual( { + title: 'Default (Index)', + description: 'Main template', + slug: 'index', + } ); + } ); + + it( 'returns the requested default template type even when the slug is numeric', () => { + expect( + __experimentalGetDefaultTemplateType( state, '404' ) + ).toEqual( { + title: '404 (Not Found)', + description: 'Applied when content cannot be found', + slug: '404', + } ); + } ); + } ); + + describe( '__experimentalGetTemplateInfo', () => { + const state = { editorSettings: { defaultTemplateTypes } }; + + it( 'should return an empty object if no template is passed', () => { + expect( __experimentalGetTemplateInfo( state, null ) ).toEqual( + {} + ); + expect( __experimentalGetTemplateInfo( state, undefined ) ).toEqual( + {} + ); + expect( __experimentalGetTemplateInfo( state, false ) ).toEqual( + {} + ); + } ); + + it( 'should return the default title if none is defined on the template', () => { + expect( + __experimentalGetTemplateInfo( state, { slug: 'index' } ).title + ).toEqual( 'Default (Index)' ); + } ); + + it( 'should return the rendered title if defined on the template', () => { + expect( + __experimentalGetTemplateInfo( state, { + slug: 'index', + title: { rendered: 'test title' }, + } ).title + ).toEqual( 'test title' ); + } ); + + it( 'should return the slug if no title is found', () => { + expect( + __experimentalGetTemplateInfo( state, { + slug: 'not a real template', + } ).title + ).toEqual( 'not a real template' ); + } ); + + it( 'should return the default description if none is defined on the template', () => { + expect( + __experimentalGetTemplateInfo( state, { slug: 'index' } ) + .description + ).toEqual( 'Main template' ); + } ); + + it( 'should return the raw excerpt as description if defined on the template', () => { + expect( + __experimentalGetTemplateInfo( state, { + slug: 'index', + excerpt: { raw: 'test description' }, + } ).description + ).toEqual( 'test description' ); + } ); + + it( 'should return both a title and a description', () => { + expect( + __experimentalGetTemplateInfo( state, { slug: 'index' } ) + ).toEqual( { + title: 'Default (Index)', + description: 'Main template', + } ); + + expect( + __experimentalGetTemplateInfo( state, { + slug: 'index', + title: { rendered: 'test title' }, + } ) + ).toEqual( { + title: 'test title', + description: 'Main template', + } ); + + expect( + __experimentalGetTemplateInfo( state, { + slug: 'index', + excerpt: { raw: 'test description' }, + } ) + ).toEqual( { + title: 'Default (Index)', + description: 'test description', + } ); + + expect( + __experimentalGetTemplateInfo( state, { + slug: 'index', + title: { rendered: 'test title' }, + excerpt: { raw: 'test description' }, + } ) + ).toEqual( { + title: 'test title', + description: 'test description', + } ); + } ); + } ); } ); diff --git a/packages/element/package.json b/packages/element/package.json index 834e077d676a21..323273d60e83d1 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -25,7 +25,7 @@ "types": "build-types", "sideEffects": false, "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@types/react": "^16.9.0", "@types/react-dom": "^16.9.0", "@wordpress/escape-html": "file:../escape-html", diff --git a/packages/env/CHANGELOG.md b/packages/env/CHANGELOG.md index 2b5160734add75..3bf6a54396b951 100644 --- a/packages/env/CHANGELOG.md +++ b/packages/env/CHANGELOG.md @@ -2,6 +2,25 @@ ## Unreleased +### Breaking Changes + +- `wp-env start` is now the only command which writes to the docker configuration files. Previously, running any command would also parse the config and then write it to the correct location. Now, other commands still parse the config, but they will not overwrite the confugiration which was set by wp-env start. This allows parameters to be passed to wp-env start which can affect the configuration. + +### Enhancement + +- Update nodegit dependency to 0.27.0, the earlier version does not have pre-built binaries for Node 14.15.0 LTS. Upgrading provides support without requiring building nodegit locally. +- Allow WP_HOME wp-config value to be set to a custom port other than the default for the docker instance. +- Append the instance URL to output of `wp-env start`. + +### New feature + +- Add support for setting the PHP version used for the WordPress instance. For example, test PHP 8 with `"phpVersion": 8.0` in wp-env.json. +- Add Xdebug 3 to the development environment. You can enable Xdebug with `wp-env start --xdebug` (for debug mode) or `wp-env start --xdebug=develop,coverage` for custom modes. + +### Bug Fixes + +- ZIP-based plugin sources are now downloaded to a directory using the basename of the URL instead of the full URL path. This prevents HTML encoded characters in the URL (like "/") from being improperly encoded into the filesystem. This fixes the issue where many .zip sources broke because files with these badly formatted characters were not loaded as assets. + ## 2.0.0 (2020-09-03) ### Breaking Changes diff --git a/packages/env/README.md b/packages/env/README.md index 7daa813fd92559..d5667f6753a147 100644 --- a/packages/env/README.md +++ b/packages/env/README.md @@ -179,7 +179,7 @@ Start `wp-env` in debug mode wp-env start --debug ``` -`wp-env` will output its config which includes `dockerComposeConfigPath`. +`wp-env` will output its config which includes `dockerComposeConfigPath`. ```sh ℹ Config: @@ -188,13 +188,58 @@ wp-env start --debug ... ``` +## Using Xdebug + +Xdebug is installed in the wp-env environment, but it is turned off by default. To enable Xdebug, you can use the `--xdebug` flag with the `wp-env start` command. Here is a reference to how the flag works: + +```sh +# Sets the Xdebug mode to "debug" (for step debugging): +wp-env start --xdebug + +# Sets the Xdebug mode to "off": +wp-env start + +# Enables each of the Xdebug modes listed: +wp-env start --xdebug=profile,trace,debug +``` + +You can see a reference on each of the Xdebug modes and what they do in the [Xdebug documentation](https://xdebug.org/docs/all_settings#mode). + +### Xdebug IDE support + +To connect to Xdebug from your IDE, you can use these IDE settings. This bit of JSON was tested for VS Code's `launch.json` format (which you can [learn more about here](https://code.visualstudio.com/docs/editor/debugging#_launchjson-attributes)) along with [this PHP Debug extension](https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug). Its path mapping also points to a specific plugin -- you should update this to point to the source you are working with inside of the wp-env instance. + +You should only have to translate `port` and `pathMappings` to the format used by your own IDE. + +```json +{ + "name": "Listen for XDebug", + "type": "php", + "request": "launch", + "port": 9003, + "pathMappings": { + "/var/www/html/wp-content/plugins/gutenberg": "${workspaceRoot}/" + } +} +``` + +Once your IDEs Xdebug settings have been enabled, you should just have to launch the debugger, put a breakpoint on any line of PHP code, and then refresh your browser! + +Here is a summary: + +1. Start wp-env with xdebug enabled: `wp-env start --xdebug` +2. Install a suitable Xdebug extension for your IDE if it does not include one already. +3. Configure the IDE debugger to use port `9003` and the correct source files in wp-env. +4. Launch the debugger and put a breakpoint on any line of PHP code. +5. Refresh the URL wp-env is running at and the breakpoint should trigger. + ## Command reference `wp-env` creates generated files in the `wp-env` home directory. By default, this is `~/.wp-env`. The exception is Linux, where files are placed at `~/wp-env` [for compatibility with Snap Packages](https://github.com/WordPress/gutenberg/issues/20180#issuecomment-587046325). The `wp-env` home directory contains a subdirectory for each project named `/$md5_of_project_path`. To change the `wp-env` home directory, set the `WP_ENV_HOME` environment variable. For example, running `WP_ENV_HOME="something" wp-env start` will download the project files to the directory `./something/$md5_of_project_path` (relative to the current directory). ### `wp-env start` -The start command installs and initalizes the WordPress environment, which includes downloading any specified remote sources. By default, `wp-env` will not update or re-configure the environment except when the configuration file changes. Tell `wp-env` to update sources and apply the configuration options again with `wp-env start --update`. This will not overrwrite any existing content. +The start command installs and initializes the WordPress environment, which includes downloading any specified remote sources. By default, `wp-env` will not update or re-configure the environment except when the configuration file changes. Tell `wp-env` to update sources and apply the configuration options again with `wp-env start --update`. This will not overwrite any existing content. ```sh wp-env start @@ -202,12 +247,20 @@ wp-env start Starts WordPress for development on port 8888 (override with WP_ENV_PORT) and tests on port 8889 (override with WP_ENV_TESTS_PORT). The current working directory must be a WordPress installation, a plugin, a theme, or contain a -.wp-env.json file. After first insall, use the '--update' flag to download updates -to mapped sources and to re-apply WordPress configuration options. +.wp-env.json file. After first install, use the '--update' flag to download +updates to mapped sources and to re-apply WordPress configuration options. Options: + --help Show help [boolean] + --version Show version number [boolean] + --debug Enable debug output. [boolean] [default: false] --update Download source updates and apply WordPress configuration. [boolean] [default: false] + --xdebug Enables Xdebug. If not passed, Xdebug is turned off. If no modes + are set, uses "debug". You may set multiple Xdebug modes by passing + them in a comma-separated list: `--xdebug=develop,coverage`. See + https://xdebug.org/docs/all_settings#mode for information about + Xdebug modes. [string] ``` ### `wp-env stop` @@ -235,10 +288,15 @@ Positionals: ```sh wp-env run <container> [command..] -Runs an arbitrary command in one of the underlying Docker containers. For -example, it can be useful for running wp cli commands. You can also use it to -open shell sessions like bash and the WordPress shell in the WordPress instance. -For example, `wp-env run cli bash` will open bash in the development WordPress +Runs an arbitrary command in one of the underlying Docker containers. The +"container" param should reference one of the underlying Docker services like +"development", "tests", or "cli". To run a wp-cli command, use the "cli" or +"tests-cli" service. You can also use this command to open shell sessions like +bash and the WordPress shell in the WordPress instance. For example, `wp-env run +cli bash` will open bash in the development WordPress instance. When using long +commands with arguments and quotation marks, you need to wrap the "command" +param in quotation marks. For example: `wp-env run tests-cli "wp post create +--post_type=page --post_title='Test'"` will create a post on the tests WordPress instance. Positionals: @@ -253,6 +311,8 @@ Options: For example: +#### Displaying the users on the development instance: + ```sh wp-env run cli wp user list ⠏ Running `wp user list` in 'cli'. @@ -263,6 +323,19 @@ ID user_login display_name user_email user_registered roles ✔ Ran `wp user list` in 'cli'. (in 2s 374ms) ``` +#### Creating a post on the tests instance: + +```sh +wp-env run tests-cli "wp post create --post_type=page --post_title='Ready'" + +ℹ Starting 'wp post create --post_type=page --post_title='Ready'' on the tests-cli container. + +Success: Created post 5. +✔ Ran `wp post create --post_type=page --post_title='Ready'` in 'tests-cli'. (in 3s 293ms) +``` + +#### Opening the WordPress shell on the tests instance and running PHP commands: + ```sh wp-env run tests-cli wp shell ℹ Starting 'wp shell' on the tests-cli container. Exit the WordPress shell with ctrl-c. @@ -308,14 +381,15 @@ You can customize the WordPress installation, plugins and themes that the develo `.wp-env.json` supports six fields for options applicable to both the tests and development instances. -| Field | Type | Default | Description | -| ------------ | -------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| `"core"` | `string\|null` | `null` | The WordPress installation to use. If `null` is specified, `wp-env` will use the latest production release of WordPress. | -| `"plugins"` | `string[]` | `[]` | A list of plugins to install and activate in the environment. | -| `"themes"` | `string[]` | `[]` | A list of themes to install in the environment. The first theme in the list will be activated. | -| `"port"` | `integer` | `8888` (`8889` for the tests instance) | The primary port number to use for the installation. You'll access the instance through the port: 'http://localhost:8888'. | -| `"config"` | `Object` | See below. | Mapping of wp-config.php constants to their desired values. | -| `"mappings"` | `Object` | `"{}"` | Mapping of WordPress directories to local directories to be mounted in the WordPress instance. | +| Field | Type | Default | Description | +| -------------- | -------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `"core"` | `string\|null` | `null` | The WordPress installation to use. If `null` is specified, `wp-env` will use the latest production release of WordPress. | +| `"phpVersion"` | `string\|null` | `null` | The PHP version to use. If `null` is specified, `wp-env` will use the default version used with production release of WordPress. | +| `"plugins"` | `string[]` | `[]` | A list of plugins to install and activate in the environment. | +| `"themes"` | `string[]` | `[]` | A list of themes to install in the environment. | +| `"port"` | `integer` | `8888` (`8889` for the tests instance) | The primary port number to use for the installation. You'll access the instance through the port: 'http://localhost:8888'. | +| `"config"` | `Object` | See below. | Mapping of wp-config.php constants to their desired values. | +| `"mappings"` | `Object` | `"{}"` | Mapping of WordPress directories to local directories to be mounted in the WordPress instance. | _Note: the port number environment variables (`WP_ENV_PORT` and `WP_ENV_TESTS_PORT`) take precedent over the .wp-env.json values._ @@ -355,9 +429,9 @@ Additionally, the key `env` is available to override any of the above options on On the development instance, `cwd` will be mapped as a plugin, `one-theme` will be mapped as a theme, KEY_1 will be set to true, and KEY_2 will be set to false. Also note that the default port, 8888, will be used as well. -On the tests instance, `cwd` is still mapped as a plugin, but no theme is mapped. Additionaly, while KEY_2 is still set to false, KEY_1 is overriden and set to false. 3000 overrides the default port as well. +On the tests instance, `cwd` is still mapped as a plugin, but no theme is mapped. Additionally, while KEY_2 is still set to false, KEY_1 is overridden and set to false. 3000 overrides the default port as well. -This gives you a lot of power to change the options appliciable to each environment. +This gives you a lot of power to change the options applicable to each environment. ## .wp-env.override.json @@ -431,7 +505,7 @@ This is useful for integration testing: that is, testing how old versions of Wor #### Add mu-plugins and other mapped directories -You can add mu-plugins via the mapping config. The mapping config also allows you to mount a directory to any location in the wordpress install, so you could even mount a subdirectory. Note here that theme-1, will not be activated, despite being the "first" mapped theme. +You can add mu-plugins via the mapping config. The mapping config also allows you to mount a directory to any location in the wordpress install, so you could even mount a subdirectory. Note here that theme-1, will not be activated. ```json { @@ -446,7 +520,7 @@ You can add mu-plugins via the mapping config. The mapping config also allows yo #### Avoid activating plugins or themes on the instance -Since all plugins in the `plugins` key are activated by default, you should use the `mappings` key to avoid this behavior. This might be helpful if you have a test plugin that should not be activated all the time. The same applies for a theme which should not be activated. +Since all plugins in the `plugins` key are activated by default, you should use the `mappings` key to avoid this behavior. This might be helpful if you have a test plugin that should not be activated all the time. ```json { @@ -488,4 +562,15 @@ You can tell `wp-env` to use a custom port number so that your instance does not } ``` +#### Specific PHP Version + +You can tell `wp-env` to use a specific PHP version for compatibility and testing. This can also be set via the environment variable `WP_ENV_PHP_VERSION`. + +```json +{ + "phpVersion": "7.2", + "plugins": [ "." ] +} +``` + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/env/lib/build-docker-compose-config.js b/packages/env/lib/build-docker-compose-config.js index 54e047fcaff9b0..8f97225a7a1b08 100644 --- a/packages/env/lib/build-docker-compose-config.js +++ b/packages/env/lib/build-docker-compose-config.js @@ -113,6 +113,51 @@ module.exports = function buildDockerComposeConfig( config ) { const developmentPorts = `\${WP_ENV_PORT:-${ config.env.development.port }}:80`; const testsPorts = `\${WP_ENV_TESTS_PORT:-${ config.env.tests.port }}:80`; + // Set the WordPress, WP-CLI, PHPUnit PHP version if defined. + const developmentPhpVersion = config.env.development.phpVersion + ? config.env.development.phpVersion + : ''; + const testsPhpVersion = config.env.tests.phpVersion + ? config.env.tests.phpVersion + : ''; + + // Set the WordPress images with the PHP version tag. + const developmentWpImage = `wordpress${ + developmentPhpVersion ? ':php' + developmentPhpVersion : '' + }`; + const testsWpImage = `wordpress${ + testsPhpVersion ? ':php' + testsPhpVersion : '' + }`; + // Set the WordPress CLI images with the PHP version tag. + const developmentWpCliImage = `wordpress:cli${ + ! developmentPhpVersion || developmentPhpVersion.length === 0 + ? '' + : '-php' + developmentPhpVersion + }`; + const testsWpCliImage = `wordpress:cli${ + ! testsPhpVersion || testsPhpVersion.length === 0 + ? '' + : '-php' + testsPhpVersion + }`; + + // Defaults are to use the most recent version of PHPUnit that provides + // support for the specified version of PHP. + // PHP Unit is assumed to be for Tests so use the testsPhpVersion. + let phpunitTag = 'latest'; + const phpunitPhpVersion = '-php-' + testsPhpVersion + '-fpm'; + if ( testsPhpVersion === '5.6' ) { + phpunitTag = '5' + phpunitPhpVersion; + } else if ( testsPhpVersion === '7.0' ) { + phpunitTag = '6' + phpunitPhpVersion; + } else if ( testsPhpVersion === '7.1' ) { + phpunitTag = '7' + phpunitPhpVersion; + } else if ( [ '7.2', '7.3', '7.4' ].indexOf( testsPhpVersion ) >= 0 ) { + phpunitTag = '8' + phpunitPhpVersion; + } else if ( testsPhpVersion === '8.0' ) { + phpunitTag = '9' + phpunitPhpVersion; + } + const phpunitImage = `wordpressdevelop/phpunit:${ phpunitTag }`; + // The www-data user in wordpress:cli has a different UID (82) to the // www-data user in wordpress (33). Ensure we use the wordpress www-data // user for CLI commands. @@ -131,8 +176,9 @@ module.exports = function buildDockerComposeConfig( config ) { volumes: [ 'mysql:/var/lib/mysql' ], }, wordpress: { + build: '.', depends_on: [ 'mysql' ], - image: 'wordpress', + image: developmentWpImage, ports: [ developmentPorts ], environment: { WORDPRESS_DB_NAME: 'wordpress', @@ -141,7 +187,7 @@ module.exports = function buildDockerComposeConfig( config ) { }, 'tests-wordpress': { depends_on: [ 'mysql' ], - image: 'wordpress', + image: testsWpImage, ports: [ testsPorts ], environment: { WORDPRESS_DB_NAME: 'tests-wordpress', @@ -150,13 +196,13 @@ module.exports = function buildDockerComposeConfig( config ) { }, cli: { depends_on: [ 'wordpress' ], - image: 'wordpress:cli', + image: developmentWpCliImage, volumes: developmentMounts, user: cliUser, }, 'tests-cli': { depends_on: [ 'tests-wordpress' ], - image: 'wordpress:cli', + image: testsWpCliImage, volumes: testsMounts, user: cliUser, }, @@ -165,7 +211,7 @@ module.exports = function buildDockerComposeConfig( config ) { volumes: [ `${ config.configDirectoryPath }:/app` ], }, phpunit: { - image: 'wordpressdevelop/phpunit:${LOCAL_PHP-latest}', + image: phpunitImage, depends_on: [ 'tests-wordpress' ], volumes: [ ...testsMounts, diff --git a/packages/env/lib/cli.js b/packages/env/lib/cli.js index bf655e5ed63ba1..2ba659969fe143 100644 --- a/packages/env/lib/cli.js +++ b/packages/env/lib/cli.js @@ -11,6 +11,7 @@ const terminalLink = require( 'terminal-link' ); * Internal dependencies */ const env = require( './env' ); +const parseXdebugMode = require( './parse-xdebug-mode' ); // Colors const boldWhite = chalk.bold.white; @@ -90,7 +91,7 @@ module.exports = function cli() { ) }} (override with WP_ENV_PORT) and tests on port {bold.underline ${ terminalLink( '8889', 'http://localhost:8889' - ) }} (override with WP_ENV_TESTS_PORT). The current working directory must be a WordPress installation, a plugin, a theme, or contain a .wp-env.json file. After first insall, use the '--update' flag to download updates to mapped sources and to re-apply WordPress configuration options.` + ) }} (override with WP_ENV_TESTS_PORT). The current working directory must be a WordPress installation, a plugin, a theme, or contain a .wp-env.json file. After first install, use the '--update' flag to download updates to mapped sources and to re-apply WordPress configuration options.` ), ( args ) => { args.option( 'update', { @@ -99,6 +100,12 @@ module.exports = function cli() { 'Download source updates and apply WordPress configuration.', default: false, } ); + args.option( 'xdebug', { + describe: + 'Enables Xdebug. If not passed, Xdebug is turned off. If no modes are set, uses "debug". You may set multiple Xdebug modes by passing them in a comma-separated list: `--xdebug=develop,coverage`. See https://xdebug.org/docs/all_settings#mode for information about Xdebug modes.', + coerce: parseXdebugMode, + type: 'string', + } ); }, withSpinner( env.start ) ); @@ -147,7 +154,7 @@ module.exports = function cli() { ); yargs.command( 'run <container> [command..]', - 'Runs an arbitrary command in one of the underlying Docker containers. For example, it can be useful for running wp cli commands. You can also use it to open shell sessions like bash and the WordPress shell in the WordPress instance. For example, `wp-env run cli bash` will open bash in the development WordPress instance.', + 'Runs an arbitrary command in one of the underlying Docker containers. The "container" param should reference one of the underlying Docker services like "development", "tests", or "cli". To run a wp-cli command, use the "cli" or "tests-cli" service. You can also use this command to open shell sessions like bash and the WordPress shell in the WordPress instance. For example, `wp-env run cli bash` will open bash in the development WordPress instance. When using long commands with arguments and quotation marks, you need to wrap the "command" param in quotation marks. For example: `wp-env run tests-cli "wp post create --post_type=page --post_title=\'Test\'"` will create a post on the tests WordPress instance.', ( args ) => { args.positional( 'container', { type: 'string', diff --git a/packages/env/lib/commands/start.js b/packages/env/lib/commands/start.js index 139932fc8ee2e8..0d2b2b717b0053 100644 --- a/packages/env/lib/commands/start.js +++ b/packages/env/lib/commands/start.js @@ -42,12 +42,18 @@ const CONFIG_CACHE_KEY = 'config_checksum'; * @param {Object} options.spinner A CLI spinner which indicates progress. * @param {boolean} options.debug True if debug mode is enabled. * @param {boolean} options.update If true, update sources. + * @param {string} options.xdebug The Xdebug mode to set. */ -module.exports = async function start( { spinner, debug, update } ) { +module.exports = async function start( { spinner, debug, update, xdebug } ) { spinner.text = 'Reading configuration.'; await checkForLegacyInstall( spinner ); - const config = await initConfig( { spinner, debug } ); + const config = await initConfig( { + spinner, + debug, + xdebug, + writeChanges: true, + } ); if ( ! config.detectedLocalConfig ) { const { configDirectoryPath } = config; @@ -175,7 +181,10 @@ module.exports = async function start( { spinner, debug, update } ) { } ); } - spinner.text = 'WordPress started.'; + const siteUrl = config.env.development.config.WP_SITEURL; + spinner.text = 'WordPress started'.concat( + siteUrl ? ` at ${ siteUrl }.` : '.' + ); }; /** diff --git a/packages/env/lib/config/config.js b/packages/env/lib/config/config.js index 487d82f83d9bfc..784ad2e9af2728 100644 --- a/packages/env/lib/config/config.js +++ b/packages/env/lib/config/config.js @@ -26,6 +26,7 @@ const md5 = require( '../md5' ); * @property {boolean} detectedLocalConfig If true, wp-env detected local config and used it. * @property {Object.<string, WPServiceConfig>} env Specific config for different environments. * @property {boolean} debug True if debug mode is enabled. + * @property {string} phpVersion Version of PHP to use in the environments, of the format 0.0. */ /** @@ -69,6 +70,7 @@ module.exports = async function readConfig( configPath ) { // Default configuration which is overridden by .wp-env.json files. const defaultConfiguration = { core: null, + phpVersion: null, plugins: [], themes: [], port: 8888, @@ -253,13 +255,24 @@ function withOverrides( config ) { getNumberFromEnvVariable( 'WP_ENV_TESTS_PORT' ) || config.env.tests.port; + // Override PHP version with environment variable. + config.env.development.phpVersion = + process.env.WP_ENV_PHP_VERSION || config.env.development.phpVersion; + config.env.tests.phpVersion = + process.env.WP_ENV_PHP_VERSION || config.env.tests.phpVersion; + const updateEnvUrl = ( configKey ) => { [ 'development', 'tests' ].forEach( ( envKey ) => { try { const baseUrl = new URL( config.env[ envKey ].config[ configKey ] ); - baseUrl.port = config.env[ envKey ].port; + + // Don't overwrite the port of WP_HOME when set. + if ( ! ( configKey === 'WP_HOME' && !! baseUrl.port ) ) { + baseUrl.port = config.env[ envKey ].port; + } + config.env[ envKey ].config[ configKey ] = baseUrl.toString(); } catch ( error ) { throw new ValidationError( diff --git a/packages/env/lib/config/parse-config.js b/packages/env/lib/config/parse-config.js index 4d1e0333a532b1..a0877fcb059451 100644 --- a/packages/env/lib/config/parse-config.js +++ b/packages/env/lib/config/parse-config.js @@ -35,6 +35,7 @@ const HOME_PATH_PREFIX = `~${ path.sep }`; module.exports = function parseConfig( config, options ) { return { port: config.port, + phpVersion: config.phpVersion, coreSource: includeTestsPath( parseSourceString( config.core, options ), options @@ -103,7 +104,8 @@ function parseSourceString( sourceString, { workDirectoryPath } ) { ); const basename = wpOrgFields ? encodeURIComponent( wpOrgFields[ 1 ] ) - : encodeURIComponent( zipFields[ 1 ] ); + : encodeURIComponent( path.basename( zipFields[ 1 ] ) ); + return { type: 'zip', url: sourceString, @@ -112,14 +114,21 @@ function parseSourceString( sourceString, { workDirectoryPath } ) { }; } - const gitHubFields = sourceString.match( /^([^\/]+)\/([^#]+)(?:#(.+))?$/ ); + const gitHubFields = sourceString.match( + /^([^\/]+)\/([^#\/]+)(\/([^#]+))?(?:#(.+))?$/ + ); if ( gitHubFields ) { return { type: 'git', url: `https://github.com/${ gitHubFields[ 1 ] }/${ gitHubFields[ 2 ] }.git`, - ref: gitHubFields[ 3 ] || 'master', - path: path.resolve( workDirectoryPath, gitHubFields[ 2 ] ), - basename: gitHubFields[ 2 ], + ref: gitHubFields[ 5 ] || 'master', + path: path.resolve( + workDirectoryPath, + gitHubFields[ 2 ], + gitHubFields[ 4 ] || '.' + ), + clonePath: path.resolve( workDirectoryPath, gitHubFields[ 2 ] ), + basename: gitHubFields[ 4 ] || gitHubFields[ 2 ], }; } diff --git a/packages/env/lib/config/test/__snapshots__/config.js.snap b/packages/env/lib/config/test/__snapshots__/config.js.snap index 1420e8ab83f8d1..db2748f02c3329 100644 --- a/packages/env/lib/config/test/__snapshots__/config.js.snap +++ b/packages/env/lib/config/test/__snapshots__/config.js.snap @@ -22,6 +22,7 @@ Object { }, "coreSource": null, "mappings": Object {}, + "phpVersion": null, "pluginSources": Array [], "port": 2000, "themeSources": Array [], @@ -43,6 +44,7 @@ Object { }, "coreSource": null, "mappings": Object {}, + "phpVersion": null, "pluginSources": Array [], "port": 1000, "themeSources": Array [], diff --git a/packages/env/lib/config/test/config.js b/packages/env/lib/config/test/config.js index 3fd20c70108225..bc9b793da86282 100644 --- a/packages/env/lib/config/test/config.js +++ b/packages/env/lib/config/test/config.js @@ -450,6 +450,63 @@ describe( 'readConfig', () => { expect( config.env.tests ).toMatchObject( matchObj ); } ); + it( 'should parse zip sources', async () => { + readFile.mockImplementation( () => + Promise.resolve( + JSON.stringify( { + plugins: [ + 'https://www.example.com/test/path/to/gutenberg.zip', + 'https://www.example.com/test/path/to/gutenberg.8.1.0.zip', + 'https://www.example.com/test/path/to/twentytwenty.zip', + 'https://www.example.com/test/path/to/twentytwenty.1.3.zip', + 'https://example.com/twentytwenty.1.3.zip', + ], + } ) + ) + ); + const config = await readConfig( '.wp-env.json' ); + const matchObj = { + pluginSources: [ + { + type: 'zip', + url: + 'https://www.example.com/test/path/to/gutenberg.zip', + path: expect.stringMatching( /^\/.*gutenberg$/ ), + basename: 'gutenberg', + }, + { + type: 'zip', + url: + 'https://www.example.com/test/path/to/gutenberg.8.1.0.zip', + path: expect.stringMatching( /^\/.*gutenberg.8.1.0$/ ), + basename: 'gutenberg.8.1.0', + }, + { + type: 'zip', + url: + 'https://www.example.com/test/path/to/twentytwenty.zip', + path: expect.stringMatching( /^\/.*twentytwenty$/ ), + basename: 'twentytwenty', + }, + { + type: 'zip', + url: + 'https://www.example.com/test/path/to/twentytwenty.1.3.zip', + path: expect.stringMatching( /^\/.*twentytwenty.1.3$/ ), + basename: 'twentytwenty.1.3', + }, + { + type: 'zip', + url: 'https://example.com/twentytwenty.1.3.zip', + path: expect.stringMatching( /^\/.*twentytwenty.1.3$/ ), + basename: 'twentytwenty.1.3', + }, + ], + }; + expect( config.env.development ).toMatchObject( matchObj ); + expect( config.env.tests ).toMatchObject( matchObj ); + } ); + it( 'should throw a validaton error if there is an unknown source', async () => { readFile.mockImplementation( () => Promise.resolve( JSON.stringify( { plugins: [ 'invalid' ] } ) ) @@ -731,6 +788,42 @@ describe( 'readConfig', () => { } ); } ); + it( 'should not overwrite port number for WP_HOME if set', async () => { + readFile.mockImplementation( () => + Promise.resolve( + JSON.stringify( { + port: 1000, + testsPort: 2000, + config: { + WP_HOME: 'http://localhost:3000/', + }, + } ) + ) + ); + const config = await readConfig( '.wp-env.json' ); + // Custom port is overriden while testsPort gets the deault value. + expect( config ).toMatchObject( { + env: { + development: { + port: 1000, + config: { + WP_TESTS_DOMAIN: 'http://localhost:1000/', + WP_SITEURL: 'http://localhost:1000/', + WP_HOME: 'http://localhost:3000/', + }, + }, + tests: { + port: 2000, + config: { + WP_TESTS_DOMAIN: 'http://localhost:2000/', + WP_SITEURL: 'http://localhost:2000/', + WP_HOME: 'http://localhost:3000/', + }, + }, + }, + } ); + } ); + it( 'should throw an error if the port number environment variable is invalid', async () => { readFile.mockImplementation( () => Promise.resolve( JSON.stringify( {} ) ) diff --git a/packages/env/lib/config/validate-config.js b/packages/env/lib/config/validate-config.js index 6a50904cb52f81..c3aa849e6322d1 100644 --- a/packages/env/lib/config/validate-config.js +++ b/packages/env/lib/config/validate-config.js @@ -70,6 +70,19 @@ function validateConfig( config, envLocation ) { ); } } + + if ( + config.phpVersion && + ! ( + typeof config.phpVersion === 'string' && + config.phpVersion.length === 3 + ) + ) { + throw new ValidationError( + `Invalid .wp-env.json: "${ envPrefix }phpVersion" must be a string of the format "0.0".` + ); + } + return config; } diff --git a/packages/env/lib/download-sources.js b/packages/env/lib/download-sources.js index 12be95a58b4b9e..d74f33d826484d 100644 --- a/packages/env/lib/download-sources.js +++ b/packages/env/lib/download-sources.js @@ -14,7 +14,6 @@ const path = require( 'path' ); const pipeline = util.promisify( require( 'stream' ).pipeline ); const extractZip = util.promisify( require( 'extract-zip' ) ); const rimraf = util.promisify( require( 'rimraf' ) ); -const copyDir = util.promisify( require( 'copy-dir' ) ); /** * @typedef {import('./config').Config} Config @@ -134,11 +133,11 @@ async function downloadGitSource( source, { onProgress, spinner, debug } ) { log( 'Cloning or getting the repo.' ); const repository = await NodeGit.Clone( source.url, - source.path, + source.clonePath, gitFetchOptions ).catch( () => { log( 'Repo already exists, get it.' ); - return NodeGit.Repository.open( source.path ); + return NodeGit.Repository.open( source.clonePath ); } ); log( 'Fetching the specified ref.' ); @@ -195,18 +194,41 @@ async function downloadZipSource( source, { onProgress, spinner, debug } ) { ); await pipeline( responseStream, zipFile ); - log( 'Extracting to temporary folder.' ); - const dirName = `${ source.path }.temp`; - await extractZip( zipName, { dir: dirName } ); - - log( 'Copying to mounted folder and cleaning up.' ); - await Promise.all( [ - rimraf( zipName ), - ...( await fs.promises.readdir( dirName ) ).map( ( file ) => - copyDir( path.join( dirName, file ), source.path ) - ), - ] ); - await rimraf( dirName ); + log( 'Extracting to temporary directory.' ); + const tempDir = `${ source.path }.temp`; + await extractZip( zipName, { dir: tempDir } ); + + const files = ( + await Promise.all( [ + rimraf( zipName ), + rimraf( source.path ), + fs.promises.readdir( tempDir ), + ] ) + )[ 2 ]; + + /** + * The plugin container is the extracted directory which is the direct parent + * of the contents of the plugin. It seems a zip file can have two fairly + * common approaches to where the content lives: + * 1. The .zip is the direct container of the files. So after extraction, the + * extraction directory contains plugin contents. + * 2. The .zip contains a directory with the same name which is the container. + * So after extraction, the extraction directory contains another directory. + * That subdirectory is the actual container of the plugin contents. + * + * We support both situations with the following check. + */ + let pluginContainer = tempDir; + const firstSubItem = path.join( tempDir, files[ 0 ] ); + if ( + files.length === 1 && + ( await fs.promises.lstat( firstSubItem ) ).isDirectory() + ) { + // In this case, only one sub directory exists, so use that as the container. + pluginContainer = firstSubItem; + } + await fs.promises.rename( pluginContainer, source.path ); + await rimraf( tempDir ); onProgress( 1 ); } diff --git a/packages/env/lib/init-config.js b/packages/env/lib/init-config.js index 02f4f3ed5e6cd7..68e16781d94e9e 100644 --- a/packages/env/lib/init-config.js +++ b/packages/env/lib/init-config.js @@ -2,8 +2,10 @@ * External dependencies */ const path = require( 'path' ); -const fs = require( 'fs' ).promises; +const { writeFile, mkdir } = require( 'fs' ).promises; +const { existsSync } = require( 'fs' ); const yaml = require( 'js-yaml' ); +const os = require( 'os' ); /** * Internal dependencies @@ -20,23 +22,32 @@ const buildDockerComposeConfig = require( './build-docker-compose-config' ); * ./.wp-env.json, creates ~/.wp-env, and creates ~/.wp-env/docker-compose.yml. * * @param {Object} options - * @param {Object} options.spinner A CLI spinner which indicates progress. - * @param {boolean} options.debug True if debug mode is enabled. - * + * @param {Object} options.spinner A CLI spinner which indicates progress. + * @param {boolean} options.debug True if debug mode is enabled. + * @param {string} options.xdebug The Xdebug mode to set. Defaults to "off". + * @param {boolean} options.writeChanges If true, writes the parsed config to the + * required docker files like docker-compose + * and Dockerfile. By default, this is false + * and only the `start` command writes any + * changes. * @return {WPConfig} The-env config object. */ -module.exports = async function initConfig( { spinner, debug } ) { +module.exports = async function initConfig( { + spinner, + debug, + xdebug = 'off', + writeChanges = false, +} ) { const configPath = path.resolve( '.wp-env.json' ); const config = await readConfig( configPath ); config.debug = debug; - await fs.mkdir( config.workDirectoryPath, { recursive: true } ); + // Adding this to the config allows the start command to understand that the + // config has changed when only the xdebug param has changed. This is needed + // so that Docker will rebuild the image whenever the xdebug flag changes. + config.xdebug = xdebug; const dockerComposeConfig = buildDockerComposeConfig( config ); - await fs.writeFile( - config.dockerComposeConfigPath, - yaml.dump( dockerComposeConfig ) - ); if ( config.debug ) { spinner.info( @@ -53,5 +64,55 @@ module.exports = async function initConfig( { spinner, debug } ) { spinner.start(); } + /** + * We avoid writing changes most of the time so that we can better pass params + * to the start command. For example, say you start wp-env with Xdebug enabled. + * If you then run another command, like opening bash in the wp instance, it + * would turn off Xdebug in the Dockerfile because it wouldn't have the --xdebug + * arg. This basically makes it such that wp-env start is the only command + * which updates any of the Docker configuration. + */ + if ( writeChanges ) { + await mkdir( config.workDirectoryPath, { recursive: true } ); + + await writeFile( + config.dockerComposeConfigPath, + yaml.dump( dockerComposeConfig ) + ); + + await writeFile( + path.resolve( config.workDirectoryPath, 'Dockerfile' ), + dockerFileContents( + dockerComposeConfig.services.wordpress.image, + xdebug + ) + ); + } else if ( ! existsSync( config.workDirectoryPath ) ) { + spinner.fail( + 'wp-env has not yet been initalized. Please run `wp-env start` to install the WordPress instance before using any other commands. This is only necessary to set up the environment for the first time; it is typically not necessary for the instance to be running after that in order to use other commands.' + ); + process.exit( 1 ); + } + return config; }; + +function dockerFileContents( image, xdebugMode ) { + const isLinux = os.type() === 'Linux'; + // Discover client host does not appear to work on macOS with Docker. + const clientDetectSettings = isLinux + ? 'xdebug.discover_client_host=true' + : 'xdebug.client_host="host.docker.internal"'; + + return `FROM ${ image } + +RUN apt -qy install $PHPIZE_DEPS \\ + && pecl install xdebug \\ + && docker-php-ext-enable xdebug + +RUN touch /usr/local/etc/php/php.ini +RUN echo 'xdebug.start_with_request=yes' >> /usr/local/etc/php/php.ini +RUN echo 'xdebug.mode=${ xdebugMode }' >> /usr/local/etc/php/php.ini +RUN echo '${ clientDetectSettings }' >> /usr/local/etc/php/php.ini +`; +} diff --git a/packages/env/lib/parse-xdebug-mode.js b/packages/env/lib/parse-xdebug-mode.js new file mode 100644 index 00000000000000..2be7c8a5be5cd3 --- /dev/null +++ b/packages/env/lib/parse-xdebug-mode.js @@ -0,0 +1,47 @@ +// See https://xdebug.org/docs/all_settings#mode +const XDEBUG_MODES = [ + 'develop', + 'coverage', + 'debug', + 'gcstats', + 'profile', + 'trace', +]; + +/** + * Custom parsing for the Xdebug mode set via yargs. This function ensures two things: + * 1. If the --xdebug flag was set by itself, default to 'debug'. + * 2. If the --xdebug flag includes modes, make sure they are accepted by Xdebug. + * + * Note: ideally, we would also have this handle the case where no xdebug flag + * is set (and then turn Xdebug off). However, yargs does not pass 'undefined' + * to the coerce callback, so we cannot handle that case here. + * + * @param {string} value The user-set mode of Xdebug + * @return {string} The Xdebug mode to use with defaults applied. + */ +module.exports = function parseXdebugMode( value ) { + if ( typeof value !== 'string' ) { + throwXdebugModeError( value ); + } + + if ( value.length === 0 ) { + return 'debug'; + } + + const modes = value.split( ',' ); + modes.forEach( ( userMode ) => { + if ( ! XDEBUG_MODES.some( ( realMode ) => realMode === userMode ) ) { + throwXdebugModeError( userMode ); + } + } ); + return value; +}; + +function throwXdebugModeError( value ) { + throw new Error( + `"${ value }" is not a mode recognized by Xdebug. Valid modes are: ${ XDEBUG_MODES.join( + ', ' + ) }` + ); +} diff --git a/packages/env/package.json b/packages/env/package.json index 0cdd8b39873f05..70b426e84a16d0 100644 --- a/packages/env/package.json +++ b/packages/env/package.json @@ -39,7 +39,7 @@ "got": "^10.7.0", "inquirer": "^7.1.0", "js-yaml": "^3.13.1", - "nodegit": "^0.26.2", + "nodegit": "^0.27.0", "ora": "^4.0.2", "rimraf": "^3.0.2", "terminal-link": "^2.0.0", diff --git a/packages/env/test/parse-xdebug-mode.js b/packages/env/test/parse-xdebug-mode.js new file mode 100644 index 00000000000000..532fc6b6daae8a --- /dev/null +++ b/packages/env/test/parse-xdebug-mode.js @@ -0,0 +1,34 @@ +/** + * Internal dependencies + */ +const parseXdebugMode = require( '../lib/parse-xdebug-mode' ); + +describe( 'parseXdebugMode', () => { + it( 'throws an error if the passed value is not a string', () => { + expect( () => parseXdebugMode() ).toThrow( + 'is not a mode recognized by Xdebug' + ); + } ); + + it( 'sets the Xdebug mode to "debug" if no mode is specified', () => { + const result = parseXdebugMode( '' ); + expect( result ).toEqual( 'debug' ); + } ); + + it( 'throws an error if a given mode is not recognized, including the invalid mode in the output', () => { + const fakeMode = 'fake-mode-123'; + expect.assertions( 2 ); + // Single mode: + expect( () => parseXdebugMode( fakeMode ) ).toThrow( fakeMode ); + + // Many modes: + expect( () => + parseXdebugMode( `debug,profile,${ fakeMode }` ) + ).toThrow( fakeMode ); + } ); + + it( 'returns all modes passed', () => { + const result = parseXdebugMode( 'debug,profile,trace' ); + expect( result ).toEqual( 'debug,profile,trace' ); + } ); +} ); diff --git a/packages/escape-html/package.json b/packages/escape-html/package.json index 1bd7278bb2933f..4944ba6d6e2fc7 100644 --- a/packages/escape-html/package.json +++ b/packages/escape-html/package.json @@ -24,7 +24,7 @@ "types": "build-types", "sideEffects": false, "dependencies": { - "@babel/runtime": "^7.11.2" + "@babel/runtime": "^7.12.5" }, "publishConfig": { "access": "public" diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index fbe29bb0447f93..adee364adaf71c 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -2,6 +2,18 @@ ## Unreleased +### New Feature + +- Add `no-unsafe-wp-apis` rule to discourage usage of unsafe APIs ([#27301](https://github.com/WordPress/gutenberg/pull/27301)). + +### Enhancements + +- The bundled `wp-prettier` dependency has been upgraded from `2.0.5` to `2.2.1`. + +### Documentation + +- Include a note about the minimum version required for `node` (10.0.0) and `npm` (6.9.0). + ## 7.2.1 (2020-09-17) ### Bug Fixes diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index a47c932d403541..cd53db818d9777 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -10,6 +10,8 @@ Install the module npm install @wordpress/eslint-plugin --save-dev ``` +**Note**: This package requires `node` 10.0.0 or later, and `npm` 6.9.0 or later. It is not compatible with older versions. + ## Usage To opt-in to the default configuration, extend your own project's `.eslintrc` file: @@ -30,15 +32,15 @@ There is also `recommended-with-formatting` ruleset for projects that want to op Alternatively, you can opt-in to only the more granular rulesets offered by the plugin. These include: -- `custom` -- `es5` -- `esnext` -- `jsdoc` -- `jsx-a11y` -- `react` -- `i18n` -- `test-e2e` -- `test-unit` +- `custom` +- `es5` +- `esnext` +- `jsdoc` +- `jsx-a11y` +- `react` +- `i18n` +- `test-e2e` +- `test-unit` For example, if your project does not use React, you could consider extending including only the ESNext rules in your project using the following `extends` definition: @@ -54,21 +56,21 @@ The granular rulesets will not define any environment globals. As such, if they ### Rules -Rule|Description|Recommended ----|---|--- -[dependency-group](/packages/eslint-plugin/docs/rules/dependency-group.md)|Enforce dependencies docblocks formatting|✓ -[gutenberg-phase](docs/rules/gutenberg-phase.md)|Governs the use of the `process.env.GUTENBERG_PHASE` constant|✓ -[no-unused-vars-before-return](/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md)|Disallow assigning variable values if unused before a return|✓ -[react-no-unsafe-timeout](/packages/eslint-plugin/docs/rules/react-no-unsafe-timeout.md)|Disallow unsafe `setTimeout` in component| -[valid-sprintf](/packages/eslint-plugin/docs/rules/valid-sprintf.md)|Enforce valid sprintf usage|✓ -[no-base-control-with-label-without-id](/packages/eslint-plugin/docs/rules/no-base-control-with-label-without-id.md)| Disallow the usage of BaseControl component with a label prop set but omitting the id property|✓ -[no-unguarded-get-range-at](/packages/eslint-plugin/docs/rules/no-unguarded-get-range-at.md)|Disallow the usage of unguarded `getRangeAt` calls|✓ -[i18n-ellipsis](/packages/eslint-plugin/docs/rules/i18n-ellipsis.md)|Disallow using three dots in translatable strings|✓ -[i18n-no-collapsible-whitespace](/packages/eslint-plugin/docs/rules/i18n-no-collapsible-whitespace.md)|Disallow collapsible whitespace in translatable strings|✓ -[i18n-no-placeholders-only](/packages/eslint-plugin/docs/rules/i18n-no-placeholders-only.md)|Prevent using only placeholders in translatable strings|✓ -[i18n-no-variables](/packages/eslint-plugin/docs/rules/i18n-no-variables.md)|Enforce string literals as translation function arguments|✓ -[i18n-text-domain](/packages/eslint-plugin/docs/rules/i18n-text-domain.md)|Enforce passing valid text domains|✓ -[i18n-translator-comments](/packages/eslint-plugin/docs/rules/i18n-translator-comments.md)|Enforce adding translator comments|✓ +| Rule | Description | Recommended | +| -------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ----------- | +| [dependency-group](/packages/eslint-plugin/docs/rules/dependency-group.md) | Enforce dependencies docblocks formatting | ✓ | +| [gutenberg-phase](docs/rules/gutenberg-phase.md) | Governs the use of the `process.env.GUTENBERG_PHASE` constant | ✓ | +| [no-unused-vars-before-return](/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) | Disallow assigning variable values if unused before a return | ✓ | +| [react-no-unsafe-timeout](/packages/eslint-plugin/docs/rules/react-no-unsafe-timeout.md) | Disallow unsafe `setTimeout` in component | +| [valid-sprintf](/packages/eslint-plugin/docs/rules/valid-sprintf.md) | Enforce valid sprintf usage | ✓ | +| [no-base-control-with-label-without-id](/packages/eslint-plugin/docs/rules/no-base-control-with-label-without-id.md) | Disallow the usage of BaseControl component with a label prop set but omitting the id property | ✓ | +| [no-unguarded-get-range-at](/packages/eslint-plugin/docs/rules/no-unguarded-get-range-at.md) | Disallow the usage of unguarded `getRangeAt` calls | ✓ | +| [i18n-ellipsis](/packages/eslint-plugin/docs/rules/i18n-ellipsis.md) | Disallow using three dots in translatable strings | ✓ | +| [i18n-no-collapsible-whitespace](/packages/eslint-plugin/docs/rules/i18n-no-collapsible-whitespace.md) | Disallow collapsible whitespace in translatable strings | ✓ | +| [i18n-no-placeholders-only](/packages/eslint-plugin/docs/rules/i18n-no-placeholders-only.md) | Prevent using only placeholders in translatable strings | ✓ | +| [i18n-no-variables](/packages/eslint-plugin/docs/rules/i18n-no-variables.md) | Enforce string literals as translation function arguments | ✓ | +| [i18n-text-domain](/packages/eslint-plugin/docs/rules/i18n-text-domain.md) | Enforce passing valid text domains | ✓ | +| [i18n-translator-comments](/packages/eslint-plugin/docs/rules/i18n-translator-comments.md) | Enforce adding translator comments | ✓ | ### Legacy diff --git a/packages/eslint-plugin/configs/custom.js b/packages/eslint-plugin/configs/custom.js index 6d722d0418bbcc..bd3d2db3b7429e 100644 --- a/packages/eslint-plugin/configs/custom.js +++ b/packages/eslint-plugin/configs/custom.js @@ -4,8 +4,9 @@ module.exports = { '@wordpress/no-unused-vars-before-return': 'error', '@wordpress/no-base-control-with-label-without-id': 'error', '@wordpress/no-unguarded-get-range-at': 'error', - '@wordpress/no-global-active-element': 'warn', - '@wordpress/no-global-get-selection': 'warn', + '@wordpress/no-global-active-element': 'error', + '@wordpress/no-global-get-selection': 'error', + '@wordpress/no-global-event-listener': 'warn', }, overrides: [ { @@ -15,10 +16,15 @@ module.exports = { }, }, { - files: [ '*.test.js', '**/test/*.js' ], + files: [ + '*.test.js', + '**/test/*.js', + 'packages/e2e-test-utils/**/*.js', + ], rules: { '@wordpress/no-global-active-element': 'off', '@wordpress/no-global-get-selection': 'off', + '@wordpress/no-global-event-listener': 'off', }, }, ], diff --git a/packages/eslint-plugin/configs/jsdoc.js b/packages/eslint-plugin/configs/jsdoc.js index 9f6868355e9403..afd8a585b27dd5 100644 --- a/packages/eslint-plugin/configs/jsdoc.js +++ b/packages/eslint-plugin/configs/jsdoc.js @@ -65,6 +65,8 @@ const typescriptUtilityTypes = [ 'NodeJS', 'AsyncIterableIterator', 'NodeRequire', + 'true', + 'false', ]; module.exports = { diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-wp-apis.md b/packages/eslint-plugin/docs/rules/no-unsafe-wp-apis.md new file mode 100644 index 00000000000000..59213648efcab5 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-unsafe-wp-apis.md @@ -0,0 +1,43 @@ +# Prevent unsafe API usage (no-unsafe-wp-apis) + +Prevent unsafe APIs from `@wordpress/*` packages from being imported. + +This includes experimental and unstable APIs which are expected to change and likely to cause issues in application code. +See the [documentation](https://github.com/WordPress/gutenberg/blob/master/docs/contributors/coding-guidelines.md#experimental-and-unstable-apis). + +> **There is no support commitment for experimental and unstable APIs.** They can and will be removed or changed without advance warning, including as part of a minor or patch release. As an external consumer, you should avoid these APIs. +> … +> +> - An **experimental API** is one which is planned for eventual public availability, but is subject to further experimentation, testing, and discussion. +> - An **unstable API** is one which serves as a means to an end. It is not desired to ever be converted into a public API. + +## Rule details + +Examples of **incorrect** code for this rule: + +```js +import { __experimentalFeature } from '@wordpress/foo'; +import { __unstableFeature } from '@wordpress/bar'; +``` + +Examples of **correct** code for this rule: + +```js +import { registerBlockType } from '@wordpress/blocks'; +``` + +## Options + +The rule can be configured via an object. +This should be an object where the keys are import package names and the values are arrays of allowed unsafe imports. + +#### Example configuration + +```json +{ + "@wordpress/no-unsafe-wp-apis": [ + "error", + { "@wordpress/block-editor": [ "__experimentalBlock" ] } + ] +} +``` diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 4b03086220c42e..f57fc1c219b78c 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -19,6 +19,10 @@ "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, + "engines": { + "node": ">=10", + "npm": ">=6.9" + }, "files": [ "configs", "rules", @@ -38,7 +42,7 @@ "eslint-plugin-react": "^7.20.0", "eslint-plugin-react-hooks": "^4.0.4", "globals": "^12.0.0", - "prettier": "npm:wp-prettier@2.0.5", + "prettier": "npm:wp-prettier@2.2.1-beta-1", "requireindex": "^1.2.0" }, "peerDependencies": { diff --git a/packages/eslint-plugin/rules/__tests__/no-global-active-element.js b/packages/eslint-plugin/rules/__tests__/no-global-active-element.js index a9a50011253090..0bf38216080145 100644 --- a/packages/eslint-plugin/rules/__tests__/no-global-active-element.js +++ b/packages/eslint-plugin/rules/__tests__/no-global-active-element.js @@ -23,7 +23,12 @@ ruleTester.run( 'no-global-active-element', rule, { invalid: [ { code: 'document.activeElement;', - errors: [ { message: 'Avoid global active element' } ], + errors: [ + { + message: + 'Avoid accessing the active element with a global. Use the ownerDocument property on a node ref instead.', + }, + ], }, ], } ); diff --git a/packages/eslint-plugin/rules/__tests__/no-global-event-listener.js b/packages/eslint-plugin/rules/__tests__/no-global-event-listener.js new file mode 100644 index 00000000000000..b7434cf5b62c93 --- /dev/null +++ b/packages/eslint-plugin/rules/__tests__/no-global-event-listener.js @@ -0,0 +1,70 @@ +/** + * External dependencies + */ +import { RuleTester } from 'eslint'; + +/** + * Internal dependencies + */ +import rule from '../no-global-event-listener'; + +const ruleTester = new RuleTester( { + parserOptions: { + ecmaVersion: 6, + }, +} ); + +ruleTester.run( 'no-global-event-listener', rule, { + valid: [ + { + code: 'ownerDocument.addEventListener();', + }, + { + code: 'ownerDocument.removeEventListener();', + }, + { + code: 'defaultView.addEventListener();', + }, + { + code: 'defaultView.removeEventListener();', + }, + ], + invalid: [ + { + code: 'document.addEventListener();', + errors: [ + { + message: + 'Avoid using (add|remove)EventListener with globals. Use `ownerDocument` or `ownerDocument.defaultView` on a node ref instead.', + }, + ], + }, + { + code: 'document.removeEventListener();', + errors: [ + { + message: + 'Avoid using (add|remove)EventListener with globals. Use `ownerDocument` or `ownerDocument.defaultView` on a node ref instead.', + }, + ], + }, + { + code: 'window.addEventListener();', + errors: [ + { + message: + 'Avoid using (add|remove)EventListener with globals. Use `ownerDocument` or `ownerDocument.defaultView` on a node ref instead.', + }, + ], + }, + { + code: 'window.removeEventListener();', + errors: [ + { + message: + 'Avoid using (add|remove)EventListener with globals. Use `ownerDocument` or `ownerDocument.defaultView` on a node ref instead.', + }, + ], + }, + ], +} ); diff --git a/packages/eslint-plugin/rules/__tests__/no-global-get-selection.js b/packages/eslint-plugin/rules/__tests__/no-global-get-selection.js index 4f757e9afcd8cc..e798363fec0e4b 100644 --- a/packages/eslint-plugin/rules/__tests__/no-global-get-selection.js +++ b/packages/eslint-plugin/rules/__tests__/no-global-get-selection.js @@ -23,7 +23,12 @@ ruleTester.run( 'no-global-get-selection', rule, { invalid: [ { code: 'window.getSelection();', - errors: [ { message: 'Avoid global selection getting' } ], + errors: [ + { + message: + 'Avoid accessing the selection with a global. Use the ownerDocument.defaultView property on a node ref instead.', + }, + ], }, ], } ); diff --git a/packages/eslint-plugin/rules/__tests__/no-unsafe-wp-apis.js b/packages/eslint-plugin/rules/__tests__/no-unsafe-wp-apis.js new file mode 100644 index 00000000000000..ed7b0ce1684a4b --- /dev/null +++ b/packages/eslint-plugin/rules/__tests__/no-unsafe-wp-apis.js @@ -0,0 +1,119 @@ +/** + * External dependencies + */ +import { RuleTester } from 'eslint'; + +/** + * Internal dependencies + */ +import rule from '../no-unsafe-wp-apis'; + +const ruleTester = new RuleTester( { + parserOptions: { + sourceType: 'module', + ecmaVersion: 6, + }, +} ); + +const options = [ + { '@wordpress/package': [ '__experimentalSafe', '__unstableSafe' ] }, +]; + +ruleTester.run( 'no-unsafe-wp-apis', rule, { + valid: [ + { code: "import _ from 'lodash';", options }, + { code: "import { map } from 'lodash';", options }, + { code: "import { __experimentalFoo } from 'lodash';", options }, + { code: "import { __unstableFoo } from 'lodash';", options }, + { code: "import _, { __unstableFoo } from 'lodash';", options }, + { code: "import * as _ from 'lodash';", options }, + + { code: "import _ from './x';", options }, + { code: "import { map } from './x';", options }, + { code: "import { __experimentalFoo } from './x';", options }, + { code: "import { __unstableFoo } from './x';", options }, + { code: "import _, { __unstableFoo } from './x';", options }, + { code: "import * as _ from './x';", options }, + + { code: "import s from '@wordpress/package';", options }, + { code: "import { feature } from '@wordpress/package';", options }, + { + code: "import { __experimentalSafe } from '@wordpress/package';", + options, + }, + { + code: "import { __unstableSafe } from '@wordpress/package';", + options, + }, + { + code: + "import { feature, __experimentalSafe } from '@wordpress/package';", + options, + }, + { + code: "import s, { __experimentalSafe } from '@wordpress/package';", + options, + }, + { code: "import * as s from '@wordpress/package';", options }, + ], + + invalid: [ + { + code: "import { __experimentalUnsafe } from '@wordpress/package';", + options, + errors: [ + { + message: `Usage of \`__experimentalUnsafe\` from \`@wordpress/package\` is not allowed. +See https://developer.wordpress.org/block-editor/contributors/develop/coding-guidelines/#experimental-and-unstable-apis for details.`, + type: 'ImportSpecifier', + }, + ], + }, + { + code: "import { __experimentalSafe } from '@wordpress/unsafe';", + options, + errors: [ + { + message: `Usage of \`__experimentalSafe\` from \`@wordpress/unsafe\` is not allowed. +See https://developer.wordpress.org/block-editor/contributors/develop/coding-guidelines/#experimental-and-unstable-apis for details.`, + type: 'ImportSpecifier', + }, + ], + }, + { + code: + "import { feature, __experimentalSafe } from '@wordpress/unsafe';", + options, + errors: [ + { + message: `Usage of \`__experimentalSafe\` from \`@wordpress/unsafe\` is not allowed. +See https://developer.wordpress.org/block-editor/contributors/develop/coding-guidelines/#experimental-and-unstable-apis for details.`, + type: 'ImportSpecifier', + }, + ], + }, + { + code: + "import s, { __experimentalUnsafe } from '@wordpress/package';", + options, + errors: [ + { + message: `Usage of \`__experimentalUnsafe\` from \`@wordpress/package\` is not allowed. +See https://developer.wordpress.org/block-editor/contributors/develop/coding-guidelines/#experimental-and-unstable-apis for details.`, + type: 'ImportSpecifier', + }, + ], + }, + { + code: "import { __unstableFeature } from '@wordpress/package';", + options, + errors: [ + { + message: `Usage of \`__unstableFeature\` from \`@wordpress/package\` is not allowed. +See https://developer.wordpress.org/block-editor/contributors/develop/coding-guidelines/#experimental-and-unstable-apis for details.`, + type: 'ImportSpecifier', + }, + ], + }, + ], +} ); diff --git a/packages/eslint-plugin/rules/dependency-group.js b/packages/eslint-plugin/rules/dependency-group.js index eaaf716ccade5d..52380418ac520c 100644 --- a/packages/eslint-plugin/rules/dependency-group.js +++ b/packages/eslint-plugin/rules/dependency-group.js @@ -1,7 +1,8 @@ /** @typedef {import('estree').Comment} Comment */ /** @typedef {import('estree').Node} Node */ -module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( { +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { meta: { type: 'layout', docs: { @@ -254,4 +255,4 @@ module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( { }, }; }, -} ); +}; diff --git a/packages/eslint-plugin/rules/no-global-active-element.js b/packages/eslint-plugin/rules/no-global-active-element.js index de4905b7828347..e3377e883d4078 100644 --- a/packages/eslint-plugin/rules/no-global-active-element.js +++ b/packages/eslint-plugin/rules/no-global-active-element.js @@ -10,7 +10,8 @@ module.exports = { ) { context.report( { node, - message: 'Avoid global active element', + message: + 'Avoid accessing the active element with a global. Use the ownerDocument property on a node ref instead.', } ); }, }; diff --git a/packages/eslint-plugin/rules/no-global-event-listener.js b/packages/eslint-plugin/rules/no-global-event-listener.js new file mode 100644 index 00000000000000..6fa73eeff3c2a8 --- /dev/null +++ b/packages/eslint-plugin/rules/no-global-event-listener.js @@ -0,0 +1,35 @@ +module.exports = { + meta: { + type: 'problem', + schema: [], + }, + create( context ) { + return { + CallExpression( node ) { + const { callee } = node; + const { object, property } = callee; + + if ( ! object || ! property ) { + return; + } + + if ( object.name !== 'document' && object.name !== 'window' ) { + return; + } + + if ( + property.name !== 'addEventListener' && + property.name !== 'removeEventListener' + ) { + return; + } + + context.report( { + node, + message: + 'Avoid using (add|remove)EventListener with globals. Use `ownerDocument` or `ownerDocument.defaultView` on a node ref instead.', + } ); + }, + }; + }, +}; diff --git a/packages/eslint-plugin/rules/no-global-get-selection.js b/packages/eslint-plugin/rules/no-global-get-selection.js index ff28a7c674d76a..08b14c02f5a9e0 100644 --- a/packages/eslint-plugin/rules/no-global-get-selection.js +++ b/packages/eslint-plugin/rules/no-global-get-selection.js @@ -10,7 +10,8 @@ module.exports = { ) { context.report( { node, - message: 'Avoid global selection getting', + message: + 'Avoid accessing the selection with a global. Use the ownerDocument.defaultView property on a node ref instead.', } ); }, }; diff --git a/packages/eslint-plugin/rules/no-unsafe-wp-apis.js b/packages/eslint-plugin/rules/no-unsafe-wp-apis.js new file mode 100644 index 00000000000000..65bcc2170d45ba --- /dev/null +++ b/packages/eslint-plugin/rules/no-unsafe-wp-apis.js @@ -0,0 +1,88 @@ +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + type: 'problem', + meta: { + schema: [ + { + type: 'object', + additionalProperties: false, + patternProperties: { + '^@wordpress\\/[a-zA-Z0-9_-]+$': { + type: 'array', + uniqueItems: true, + minItems: 1, + items: { + type: 'string', + pattern: '^(?:__experimental|__unstable)', + }, + }, + }, + }, + ], + }, + create( context ) { + /** @type {AllowedImportsMap} */ + const allowedImports = + ( context.options && + typeof context.options[ 0 ] === 'object' && + context.options[ 0 ] ) || + {}; + const reporter = makeListener( { allowedImports, context } ); + + return { ImportDeclaration: reporter }; + }, +}; + +/** + * @param {Object} _ + * @param {AllowedImportsMap} _.allowedImports + * @param {import('eslint').Rule.RuleContext} _.context + * + * @return {(node: Node) => void} Listener function + */ +function makeListener( { allowedImports, context } ) { + return function reporter( node ) { + if ( node.type !== 'ImportDeclaration' ) { + return; + } + if ( typeof node.source.value !== 'string' ) { + return; + } + + const sourceModule = node.source.value.trim(); + + // Ignore non-WordPress packages + if ( ! sourceModule.startsWith( '@wordpress/' ) ) { + return; + } + + const allowedImportNames = allowedImports[ sourceModule ] || []; + + node.specifiers.forEach( ( specifierNode ) => { + if ( specifierNode.type !== 'ImportSpecifier' ) { + return; + } + + const importedName = specifierNode.imported.name; + + if ( + ! importedName.startsWith( '__unstable' ) && + ! importedName.startsWith( '__experimental' ) + ) { + return; + } + + if ( allowedImportNames.includes( importedName ) ) { + return; + } + + context.report( { + message: `Usage of \`${ importedName }\` from \`${ sourceModule }\` is not allowed.\nSee https://developer.wordpress.org/block-editor/contributors/develop/coding-guidelines/#experimental-and-unstable-apis for details.`, + node: specifierNode, + } ); + } ); + }; +} + +/** @typedef {import('estree').Node} Node */ +/** @typedef {Record<string, string[]|undefined>} AllowedImportsMap */ diff --git a/packages/eslint-plugin/tsconfig.json b/packages/eslint-plugin/tsconfig.json index 0f0a4598184f80..d292d97510c414 100644 --- a/packages/eslint-plugin/tsconfig.json +++ b/packages/eslint-plugin/tsconfig.json @@ -1,13 +1,12 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "module": "CommonJS", "rootDir": "rules", "declarationDir": "build-types" }, // NOTE: This package is being progressively typed. You are encouraged to // expand this array with files which can be type-checked. At some point in // the future, this can be simplified to an `includes` of `src/**/*`. - "files": [ - "rules/dependency-group.js" - ] + "files": [ "rules/dependency-group.js", "rules/no-unsafe-wp-apis.js" ] } diff --git a/packages/format-library/package.json b/packages/format-library/package.json index b4fee466d8cd13..6a0a0ee9f5a55a 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/format-library", - "version": "1.25.3", + "version": "1.25.4", "description": "Format library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -22,7 +22,8 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", + "@wordpress/a11y": "file:../a11y", "@wordpress/block-editor": "file:../block-editor", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", diff --git a/packages/format-library/src/code/index.js b/packages/format-library/src/code/index.js index 772fe127816210..be3c0c6f9278ba 100644 --- a/packages/format-library/src/code/index.js +++ b/packages/format-library/src/code/index.js @@ -7,7 +7,7 @@ import { RichTextToolbarButton } from '@wordpress/block-editor'; import { code as codeIcon } from '@wordpress/icons'; const name = 'core/code'; -const title = __( 'Inline Code' ); +const title = __( 'Inline code' ); export const code = { name, diff --git a/packages/format-library/src/default-formats.js b/packages/format-library/src/default-formats.js index dfc931ec41e606..412ae23f4b686d 100644 --- a/packages/format-library/src/default-formats.js +++ b/packages/format-library/src/default-formats.js @@ -11,6 +11,7 @@ import { underline } from './underline'; import { textColor } from './text-color'; import { subscript } from './subscript'; import { superscript } from './superscript'; +import { keyboard } from './keyboard'; export default [ bold, @@ -23,4 +24,5 @@ export default [ textColor, subscript, superscript, + keyboard, ]; diff --git a/packages/format-library/src/image/index.js b/packages/format-library/src/image/index.js index 57da95e91b2edc..f7a52edf1af168 100644 --- a/packages/format-library/src/image/index.js +++ b/packages/format-library/src/image/index.js @@ -3,14 +3,13 @@ */ import { Path, SVG, TextControl, Popover, Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { Component } from '@wordpress/element'; -import { insertObject } from '@wordpress/rich-text'; +import { useState } from '@wordpress/element'; +import { insertObject, useAnchorRef } from '@wordpress/rich-text'; import { MediaUpload, RichTextToolbarButton, MediaUploadCheck, } from '@wordpress/block-editor'; -import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; import { keyboardReturn } from '@wordpress/icons'; const ALLOWED_MEDIA_TYPES = [ 'image' ]; @@ -18,13 +17,6 @@ const ALLOWED_MEDIA_TYPES = [ 'image' ]; const name = 'core/image'; const title = __( 'Inline image' ); -const stopKeyPropagation = ( event ) => event.stopPropagation(); - -function getRange() { - const selection = window.getSelection(); - return selection.rangeCount ? selection.getRangeAt( 0 ) : null; -} - export const image = { name, title, @@ -38,184 +30,129 @@ export const image = { url: 'src', alt: 'alt', }, - edit: class ImageEdit extends Component { - constructor() { - super( ...arguments ); - this.onChange = this.onChange.bind( this ); - this.onKeyDown = this.onKeyDown.bind( this ); - this.openModal = this.openModal.bind( this ); - this.closeModal = this.closeModal.bind( this ); - this.anchorRef = null; - this.state = { - modal: false, - }; - } - - static getDerivedStateFromProps( props, state ) { - const { - activeObjectAttributes: { style }, - } = props; - - if ( style === state.previousStyle ) { - return null; - } - - if ( ! style ) { - return { - width: undefined, - previousStyle: style, - }; - } - - return { - width: style.replace( /\D/g, '' ), - previousStyle: style, - }; - } - - onChange( width ) { - this.setState( { width } ); - } - - onKeyDown( event ) { - if ( - [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( - event.keyCode - ) > -1 - ) { - // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. - event.stopPropagation(); - } - } - - openModal() { - this.setState( { modal: true } ); - } - - closeModal() { - this.setState( { modal: false } ); - } - - componentDidMount() { - this.anchorRef = getRange(); - } - - componentDidUpdate( prevProps ) { - // When the popover is open or when the selected image changes, - // update the anchorRef. - if ( - ( ! prevProps.isObjectActive && this.props.isObjectActive ) || - prevProps.activeObjectAttributes.url !== - this.props.activeObjectAttributes.url - ) { - this.anchorRef = getRange(); - } - } - - render() { - const { - value, - onChange, - onFocus, - isObjectActive, - activeObjectAttributes, - } = this.props; - - return ( - <MediaUploadCheck> - <RichTextToolbarButton - icon={ - <SVG - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 24 24" - > - <Path d="M4 18.5h16V17H4v1.5zM16 13v1.5h4V13h-4zM5.1 15h7.8c.6 0 1.1-.5 1.1-1.1V6.1c0-.6-.5-1.1-1.1-1.1H5.1C4.5 5 4 5.5 4 6.1v7.8c0 .6.5 1.1 1.1 1.1zm.4-8.5h7V10l-1-1c-.3-.3-.8-.3-1 0l-1.6 1.5-1.2-.7c-.3-.2-.6-.2-.9 0l-1.3 1V6.5zm0 6.1l1.8-1.3 1.3.8c.3.2.7.2.9-.1l1.5-1.4 1.5 1.4v1.5h-7v-.9z" /> - </SVG> - } - title={ title } - onClick={ this.openModal } - isActive={ isObjectActive } - /> - { this.state.modal && ( - <MediaUpload - allowedTypes={ ALLOWED_MEDIA_TYPES } - onSelect={ ( { id, url, alt, width } ) => { - this.closeModal(); - onChange( - insertObject( value, { - type: name, - attributes: { - className: `wp-image-${ id }`, - style: `width: ${ Math.min( - width, - 150 - ) }px;`, - url, - alt, - }, - } ) - ); - onFocus(); - } } - onClose={ this.closeModal } - render={ ( { open } ) => { - open(); - return null; - } } - /> - ) } - { isObjectActive && ( - <Popover - position="bottom center" - focusOnMount={ false } - anchorRef={ this.anchorRef } - > - { - // Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar - /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ - } - <form - className="block-editor-format-toolbar__image-container-content" - onKeyPress={ stopKeyPropagation } - onKeyDown={ this.onKeyDown } - onSubmit={ ( event ) => { - const newReplacements = value.replacements.slice(); - - newReplacements[ value.start ] = { - type: name, - attributes: { - ...activeObjectAttributes, - style: `width: ${ this.state.width }px;`, - }, - }; + edit: Edit, +}; - onChange( { - ...value, - replacements: newReplacements, - } ); +function InlineUI( { value, onChange, activeObjectAttributes, contentRef } ) { + const { style } = activeObjectAttributes; + const [ width, setWidth ] = useState( style.replace( /\D/g, '' ) ); + const anchorRef = useAnchorRef( { + ref: contentRef, + value, + settings: image, + } ); + + return ( + <Popover + position="bottom center" + focusOnMount={ false } + anchorRef={ anchorRef } + > + <form + className="block-editor-format-toolbar__image-container-content" + onSubmit={ ( event ) => { + const newReplacements = value.replacements.slice(); + + newReplacements[ value.start ] = { + type: name, + attributes: { + ...activeObjectAttributes, + style: `width: ${ width }px;`, + }, + }; + + onChange( { + ...value, + replacements: newReplacements, + } ); + + event.preventDefault(); + } } + > + <TextControl + className="block-editor-format-toolbar__image-container-value" + type="number" + label={ __( 'Width' ) } + value={ width } + min={ 1 } + onChange={ ( newWidth ) => setWidth( newWidth ) } + /> + <Button + icon={ keyboardReturn } + label={ __( 'Apply' ) } + type="submit" + /> + </form> + </Popover> + ); +} - event.preventDefault(); - } } - > - <TextControl - className="block-editor-format-toolbar__image-container-value" - type="number" - label={ __( 'Width' ) } - value={ this.state.width } - min={ 1 } - onChange={ this.onChange } - /> - <Button - icon={ keyboardReturn } - label={ __( 'Apply' ) } - type="submit" - /> - </form> - { /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ } - </Popover> - ) } - </MediaUploadCheck> - ); - } - }, -}; +function Edit( { + value, + onChange, + onFocus, + isObjectActive, + activeObjectAttributes, + contentRef, +} ) { + const [ isModalOpen, setIsModalOpen ] = useState( false ); + + function openModal() { + setIsModalOpen( true ); + } + + function closeModal() { + setIsModalOpen( false ); + } + + return ( + <MediaUploadCheck> + <RichTextToolbarButton + icon={ + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M4 18.5h16V17H4v1.5zM16 13v1.5h4V13h-4zM5.1 15h7.8c.6 0 1.1-.5 1.1-1.1V6.1c0-.6-.5-1.1-1.1-1.1H5.1C4.5 5 4 5.5 4 6.1v7.8c0 .6.5 1.1 1.1 1.1zm.4-8.5h7V10l-1-1c-.3-.3-.8-.3-1 0l-1.6 1.5-1.2-.7c-.3-.2-.6-.2-.9 0l-1.3 1V6.5zm0 6.1l1.8-1.3 1.3.8c.3.2.7.2.9-.1l1.5-1.4 1.5 1.4v1.5h-7v-.9z" /> + </SVG> + } + title={ title } + onClick={ openModal } + isActive={ isObjectActive } + /> + { isModalOpen && ( + <MediaUpload + allowedTypes={ ALLOWED_MEDIA_TYPES } + onSelect={ ( { id, url, alt, width: imgWidth } ) => { + closeModal(); + onChange( + insertObject( value, { + type: name, + attributes: { + className: `wp-image-${ id }`, + style: `width: ${ Math.min( + imgWidth, + 150 + ) }px;`, + url, + alt, + }, + } ) + ); + onFocus(); + } } + onClose={ closeModal } + render={ ( { open } ) => { + open(); + return null; + } } + /> + ) } + { isObjectActive && ( + <InlineUI + value={ value } + onChange={ onChange } + activeObjectAttributes={ activeObjectAttributes } + contentRef={ contentRef } + /> + ) } + </MediaUploadCheck> + ); +} diff --git a/packages/format-library/src/keyboard/index.js b/packages/format-library/src/keyboard/index.js new file mode 100644 index 00000000000000..d6a260f8e07dd0 --- /dev/null +++ b/packages/format-library/src/keyboard/index.js @@ -0,0 +1,36 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { toggleFormat } from '@wordpress/rich-text'; +import { RichTextToolbarButton } from '@wordpress/block-editor'; +import { button } from '@wordpress/icons'; + +const name = 'core/keyboard'; +const title = __( 'Keyboard input' ); + +export const keyboard = { + name, + title, + tagName: 'kbd', + className: null, + edit( { isActive, value, onChange, onFocus } ) { + function onToggle() { + onChange( toggleFormat( value, { type: name } ) ); + } + + function onClick() { + onToggle(); + onFocus(); + } + + return ( + <RichTextToolbarButton + icon={ button } + title={ title } + onClick={ onClick } + isActive={ isActive } + /> + ); + }, +}; diff --git a/packages/format-library/src/link/index.js b/packages/format-library/src/link/index.js index 6f56f9161410b7..e56e93f1743c45 100644 --- a/packages/format-library/src/link/index.js +++ b/packages/format-library/src/link/index.js @@ -2,8 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Component } from '@wordpress/element'; -import { withSpokenMessages } from '@wordpress/components'; +import { useState } from '@wordpress/element'; import { getTextContent, applyFormat, @@ -18,6 +17,7 @@ import { } from '@wordpress/block-editor'; import { decodeEntities } from '@wordpress/html-entities'; import { link as linkIcon, linkOff } from '@wordpress/icons'; +import { speak } from '@wordpress/a11y'; /** * Internal dependencies @@ -27,6 +27,93 @@ import InlineLinkUI from './inline'; const name = 'core/link'; const title = __( 'Link' ); +function Edit( { + isActive, + activeAttributes, + value, + onChange, + onFocus, + contentRef, +} ) { + const [ addingLink, setAddingLink ] = useState( false ); + + function addLink() { + const text = getTextContent( slice( value ) ); + + if ( text && isURL( text ) ) { + onChange( + applyFormat( value, { + type: name, + attributes: { url: text }, + } ) + ); + } else if ( text && isEmail( text ) ) { + onChange( + applyFormat( value, { + type: name, + attributes: { url: `mailto:${ text }` }, + } ) + ); + } else { + setAddingLink( true ); + } + } + + function stopAddingLink() { + setAddingLink( false ); + onFocus(); + } + + function onRemoveFormat() { + onChange( removeFormat( value, name ) ); + speak( __( 'Link removed.' ), 'assertive' ); + } + + return ( + <> + <RichTextShortcut type="primary" character="k" onUse={ addLink } /> + <RichTextShortcut + type="primaryShift" + character="k" + onUse={ onRemoveFormat } + /> + { isActive && ( + <RichTextToolbarButton + name="link" + icon={ linkOff } + title={ __( 'Unlink' ) } + onClick={ onRemoveFormat } + isActive={ isActive } + shortcutType="primaryShift" + shortcutCharacter="k" + /> + ) } + { ! isActive && ( + <RichTextToolbarButton + name="link" + icon={ linkIcon } + title={ title } + onClick={ addLink } + isActive={ isActive } + shortcutType="primary" + shortcutCharacter="k" + /> + ) } + { ( addingLink || isActive ) && ( + <InlineLinkUI + addingLink={ addingLink } + stopAddingLink={ stopAddingLink } + isActive={ isActive } + activeAttributes={ activeAttributes } + value={ value } + onChange={ onChange } + contentRef={ contentRef } + /> + ) } + </> + ); +} + export const link = { name, title, @@ -62,109 +149,5 @@ export const link = { }, } ); }, - edit: withSpokenMessages( - class LinkEdit extends Component { - constructor() { - super( ...arguments ); - - this.addLink = this.addLink.bind( this ); - this.stopAddingLink = this.stopAddingLink.bind( this ); - this.onRemoveFormat = this.onRemoveFormat.bind( this ); - this.state = { - addingLink: false, - }; - } - - addLink() { - const { value, onChange } = this.props; - const text = getTextContent( slice( value ) ); - - if ( text && isURL( text ) ) { - onChange( - applyFormat( value, { - type: name, - attributes: { url: text }, - } ) - ); - } else if ( text && isEmail( text ) ) { - onChange( - applyFormat( value, { - type: name, - attributes: { url: `mailto:${ text }` }, - } ) - ); - } else { - this.setState( { addingLink: true } ); - } - } - - stopAddingLink() { - this.setState( { addingLink: false } ); - this.props.onFocus(); - } - - onRemoveFormat() { - const { value, onChange, speak } = this.props; - - onChange( removeFormat( value, name ) ); - speak( __( 'Link removed.' ), 'assertive' ); - } - - render() { - const { - isActive, - activeAttributes, - value, - onChange, - } = this.props; - - return ( - <> - <RichTextShortcut - type="primary" - character="k" - onUse={ this.addLink } - /> - <RichTextShortcut - type="primaryShift" - character="k" - onUse={ this.onRemoveFormat } - /> - { isActive && ( - <RichTextToolbarButton - name="link" - icon={ linkOff } - title={ __( 'Unlink' ) } - onClick={ this.onRemoveFormat } - isActive={ isActive } - shortcutType="primaryShift" - shortcutCharacter="k" - /> - ) } - { ! isActive && ( - <RichTextToolbarButton - name="link" - icon={ linkIcon } - title={ title } - onClick={ this.addLink } - isActive={ isActive } - shortcutType="primary" - shortcutCharacter="k" - /> - ) } - { ( this.state.addingLink || isActive ) && ( - <InlineLinkUI - addingLink={ this.state.addingLink } - stopAddingLink={ this.stopAddingLink } - isActive={ isActive } - activeAttributes={ activeAttributes } - value={ value } - onChange={ onChange } - /> - ) } - </> - ); - } - } - ), + edit: Edit, }; diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index 9d190ac201c775..4c78c9d1475a99 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -1,22 +1,24 @@ -/** - * External dependencies - */ -import { uniqueId } from 'lodash'; - /** * WordPress dependencies */ -import { useMemo, useState } from '@wordpress/element'; +import { useState, useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { withSpokenMessages, Popover } from '@wordpress/components'; import { prependHTTP } from '@wordpress/url'; -import { create, insert, isCollapsed, applyFormat } from '@wordpress/rich-text'; +import { + create, + insert, + isCollapsed, + applyFormat, + useAnchorRef, +} from '@wordpress/rich-text'; import { __experimentalLinkControl as LinkControl } from '@wordpress/block-editor'; /** * Internal dependencies */ import { createLinkFormat, isValidHref } from './utils'; +import { link as settings } from './index'; function InlineLinkUI( { isActive, @@ -26,23 +28,8 @@ function InlineLinkUI( { onChange, speak, stopAddingLink, + contentRef, } ) { - /** - * A unique key is generated when switching between editing and not editing - * a link, based on: - * - * - This component may be rendered _either_ when a link is active _or_ - * when adding or editing a link. - * - It's only desirable to shift focus into the Popover when explicitly - * adding or editing a link, not when in the inline boundary of a link. - * - Focus behavior can only be controlled on a Popover at the time it - * mounts, so a new instance of the component must be mounted to - * programmatically enact the focusOnMount behavior. - * - * @type {string} - */ - const mountingKey = useMemo( uniqueId, [ addingLink ] ); - /** * Pending settings to be applied to the next link. When inserting a new * link, toggle values cannot be applied immediately, because there is not @@ -53,31 +40,6 @@ function InlineLinkUI( { */ const [ nextLinkValue, setNextLinkValue ] = useState(); - const anchorRef = useMemo( () => { - const selection = window.getSelection(); - - if ( ! selection.rangeCount ) { - return; - } - - const range = selection.getRangeAt( 0 ); - - if ( addingLink && ! isActive ) { - return range; - } - - let element = range.startContainer; - - // If the caret is right before the element, select the next element. - element = element.nextElementSibling || element; - - while ( element.nodeType !== element.ELEMENT_NODE ) { - element = element.parentNode; - } - - return element.closest( 'a' ); - }, [ addingLink, value.start, value.end ] ); - const linkValue = { url: activeAttributes.url, type: activeAttributes.type, @@ -161,11 +123,16 @@ function InlineLinkUI( { } } + const anchorRef = useAnchorRef( { ref: contentRef, value, settings } ); + + // The focusOnMount prop shouldn't evolve during render of a Popover + // otherwise it causes a render of the content. + const focusOnMount = useRef( addingLink ? 'firstElement' : false ); + return ( <Popover - key={ mountingKey } anchorRef={ anchorRef } - focusOnMount={ addingLink ? 'firstElement' : false } + focusOnMount={ focusOnMount.current } onClose={ stopAddingLink } position="bottom center" > diff --git a/packages/format-library/src/link/modal-screens/link-settings-screen.native.js b/packages/format-library/src/link/modal-screens/link-settings-screen.native.js index 569675a28176ab..b69d347e065c1f 100644 --- a/packages/format-library/src/link/modal-screens/link-settings-screen.native.js +++ b/packages/format-library/src/link/modal-screens/link-settings-screen.native.js @@ -3,7 +3,6 @@ */ import React from 'react'; import { useNavigation, useRoute } from '@react-navigation/native'; -import { View } from 'react-native'; /** * WordPress dependencies */ @@ -140,37 +139,30 @@ const LinkSettingsScreen = ( { return useMemo( () => { return ( <> - <View style={ styles.container }> - <BottomSheet.LinkCell - value={ inputValue } - onPress={ onLinkCellPressed } - /> - <BottomSheet.Cell - icon={ textColor } - label={ __( 'Link text' ) } - value={ text } - placeholder={ __( 'Add link text' ) } - onChangeValue={ setText } - onSubmit={ submit } - /> - <BottomSheet.SwitchCell - icon={ external } - label={ __( 'Open in new tab' ) } - value={ opensInNewWindow } - onValueChange={ setOpensInNewWindows } - separatorType={ 'fullWidth' } - /> - <BottomSheet.Cell - label={ __( 'Remove link' ) } - labelStyle={ styles.clearLinkButton } - separatorType={ 'none' } - onPress={ removeLink } - /> - </View> - <View - style={ { - height: listProps.safeAreaBottomInset || 1, - } } + <BottomSheet.LinkCell + value={ inputValue } + onPress={ onLinkCellPressed } + /> + <BottomSheet.Cell + icon={ textColor } + label={ __( 'Link text' ) } + value={ text } + placeholder={ __( 'Add link text' ) } + onChangeValue={ setText } + onSubmit={ submit } + /> + <BottomSheet.SwitchCell + icon={ external } + label={ __( 'Open in new tab' ) } + value={ opensInNewWindow } + onValueChange={ setOpensInNewWindows } + separatorType={ 'fullWidth' } + /> + <BottomSheet.Cell + label={ __( 'Remove link' ) } + labelStyle={ styles.clearLinkButton } + separatorType={ 'none' } + onPress={ removeLink } /> </> ); diff --git a/packages/format-library/src/link/modal.native.js b/packages/format-library/src/link/modal.native.js index fe9261933c7ae2..666547090bf053 100644 --- a/packages/format-library/src/link/modal.native.js +++ b/packages/format-library/src/link/modal.native.js @@ -19,10 +19,10 @@ const ModalLinkUI = ( { isVisible, ...restProps } ) => { return useMemo( () => { return ( <BottomSheet - isChildrenScrollable isVisible={ isVisible } hideHeader onClose={ restProps.onClose } + hasNavigation > <BottomSheet.NavigationContainer animate main> <BottomSheet.NavigationScreen name={ screens.settings }> @@ -30,6 +30,7 @@ const ModalLinkUI = ( { isVisible, ...restProps } ) => { </BottomSheet.NavigationScreen> <BottomSheet.NavigationScreen name={ screens.picker } + isScrollable fullScreen > <LinkPickerScreen /> diff --git a/packages/format-library/src/link/modal.native.scss b/packages/format-library/src/link/modal.native.scss index 3462a913be7ff6..be63cf5ffbe821 100644 --- a/packages/format-library/src/link/modal.native.scss +++ b/packages/format-library/src/link/modal.native.scss @@ -8,3 +8,9 @@ padding-right: $block-edge-to-content; padding-top: $block-edge-to-content/2; } + +.content { + padding-left: $grid-unit-20; + padding-right: $grid-unit-20; + padding-bottom: 0; +} diff --git a/packages/format-library/src/text-color/index.js b/packages/format-library/src/text-color/index.js index 2ddfcc4f15d9d6..c923c73939dad5 100644 --- a/packages/format-library/src/text-color/index.js +++ b/packages/format-library/src/text-color/index.js @@ -21,11 +21,17 @@ import { removeFormat } from '@wordpress/rich-text'; import { default as InlineColorUI, getActiveColor } from './inline'; const name = 'core/text-color'; -const title = __( 'Text Color' ); +const title = __( 'Text color' ); const EMPTY_ARRAY = []; -function TextColorEdit( { value, onChange, isActive, activeAttributes } ) { +function TextColorEdit( { + value, + onChange, + isActive, + activeAttributes, + contentRef, +} ) { const allowCustomControl = useEditorFeature( 'color.custom' ); const colors = useEditorFeature( 'color.palette' ) || EMPTY_ARRAY; const [ isAddingColor, setIsAddingColor ] = useState( false ); @@ -78,11 +84,11 @@ function TextColorEdit( { value, onChange, isActive, activeAttributes } ) { { isAddingColor && ( <InlineColorUI name={ name } - addingColor={ isAddingColor } onClose={ disableIsAddingColor } activeAttributes={ activeAttributes } value={ value } onChange={ onChange } + contentRef={ contentRef } /> ) } </> diff --git a/packages/format-library/src/text-color/inline.js b/packages/format-library/src/text-color/inline.js index a464dbdd4a24bb..b6b38e31c509c7 100644 --- a/packages/format-library/src/text-color/inline.js +++ b/packages/format-library/src/text-color/inline.js @@ -8,11 +8,11 @@ import { get } from 'lodash'; */ import { useCallback, useMemo } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; -import { getRectangleFromRange } from '@wordpress/dom'; import { applyFormat, removeFormat, getActiveFormat, + useAnchorRef, } from '@wordpress/rich-text'; import { ColorPalette, @@ -22,6 +22,11 @@ import { getColorObjectByAttributeValues, } from '@wordpress/block-editor'; +/** + * Internal dependencies + */ +import { textColor as settings } from './index'; + export function getActiveColor( formatName, formatValue, colors ) { const activeColorFormat = getActiveFormat( formatValue, formatName ); if ( ! activeColorFormat ) { @@ -41,44 +46,6 @@ export function getActiveColor( formatName, formatValue, colors ) { } } -const ColorPopoverAtLink = ( { addingColor, ...props } ) => { - // There is no way to open a text formatter popover when another one is mounted. - // The first popover will always be dismounted when a click outside happens, so we can store the - // anchor Rect during the lifetime of the component. - const anchorRect = useMemo( () => { - const selection = window.getSelection(); - const range = - selection.rangeCount > 0 ? selection.getRangeAt( 0 ) : null; - if ( ! range ) { - return; - } - - if ( addingColor ) { - return getRectangleFromRange( range ); - } - - let element = range.startContainer; - - // If the caret is right before the element, select the next element. - element = element.nextElementSibling || element; - - while ( element.nodeType !== element.ELEMENT_NODE ) { - element = element.parentNode; - } - - const closest = element.closest( 'span' ); - if ( closest ) { - return closest.getBoundingClientRect(); - } - }, [] ); - - if ( ! anchorRect ) { - return null; - } - - return <URLPopover anchorRect={ anchorRect } { ...props } />; -}; - const ColorPicker = ( { name, value, onChange } ) => { const colors = useSelect( ( select ) => { const { getSettings } = select( 'core/block-editor' ); @@ -123,16 +90,17 @@ export default function InlineColorUI( { value, onChange, onClose, - addingColor, + contentRef, } ) { + const anchorRef = useAnchorRef( { ref: contentRef, value, settings } ); return ( - <ColorPopoverAtLink + <URLPopover value={ value } - addingColor={ addingColor } onClose={ onClose } className="components-inline-color-popover" + anchorRef={ anchorRef } > <ColorPicker name={ name } value={ value } onChange={ onChange } /> - </ColorPopoverAtLink> + </URLPopover> ); } diff --git a/packages/hooks/CHANGELOG.md b/packages/hooks/CHANGELOG.md index 2d2e4c013e4440..53c17fccf741ce 100644 --- a/packages/hooks/CHANGELOG.md +++ b/packages/hooks/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +### New Feature + +- Include TypeScript type declarations ([#26430](https://github.com/WordPress/gutenberg/pull/26430)) + +### Bug Fix + +- Fix: Use own instance's `doAction` method for built-in `hookAdded` and `hookRemoved` hooks ([#26498](https://github.com/WordPress/gutenberg/pull/26498)) + ## 2.6.0 (2019-08-29) ### New Feature diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 54b8d461fc260d..70c755f3a2f942 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -21,8 +21,9 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "types": "build-types", "dependencies": { - "@babel/runtime": "^7.11.2" + "@babel/runtime": "^7.12.5" }, "publishConfig": { "access": "public" diff --git a/packages/hooks/src/createAddHook.js b/packages/hooks/src/createAddHook.js index 3d545a2e824cfc..54af960956d432 100644 --- a/packages/hooks/src/createAddHook.js +++ b/packages/hooks/src/createAddHook.js @@ -3,25 +3,30 @@ */ import validateNamespace from './validateNamespace.js'; import validateHookName from './validateHookName.js'; -import { doAction } from './'; + +/** + * @callback AddHook + * + * Adds the hook to the appropriate hooks container. + * + * @param {string} hookName Name of hook to add + * @param {string} namespace The unique namespace identifying the callback in the form `vendor/plugin/function`. + * @param {import('.').Callback} callback Function to call when the hook is run + * @param {number} [priority=10] Priority of this hook + */ /** * Returns a function which, when invoked, will add a hook. * - * @param {Object} hooks Stored hooks, keyed by hook name. + * @param {import('.').Hooks} hooks Hooks instance. + * @param {import('.').StoreKey} storeKey * - * @return {Function} Function that adds a new hook. + * @return {AddHook} Function that adds a new hook. */ -function createAddHook( hooks ) { - /** - * Adds the hook to the appropriate hooks container. - * - * @param {string} hookName Name of hook to add - * @param {string} namespace The unique namespace identifying the callback in the form `vendor/plugin/function`. - * @param {Function} callback Function to call when the hook is run - * @param {?number} priority Priority of this hook (default=10) - */ +function createAddHook( hooks, storeKey ) { return function addHook( hookName, namespace, callback, priority = 10 ) { + const hooksStore = hooks[ storeKey ]; + if ( ! validateHookName( hookName ) ) { return; } @@ -47,10 +52,11 @@ function createAddHook( hooks ) { const handler = { callback, priority, namespace }; - if ( hooks[ hookName ] ) { + if ( hooksStore[ hookName ] ) { // Find the correct insert index of the new hook. - const handlers = hooks[ hookName ].handlers; + const handlers = hooksStore[ hookName ].handlers; + /** @type {number} */ let i; for ( i = handlers.length; i > 0; i-- ) { if ( priority >= handlers[ i - 1 ].priority ) { @@ -70,7 +76,7 @@ function createAddHook( hooks ) { // we're adding would come after the current callback, there's no // problem; otherwise we need to increase the execution index of // any other runs by 1 to account for the added element. - ( hooks.__current || [] ).forEach( ( hookInfo ) => { + hooksStore.__current.forEach( ( hookInfo ) => { if ( hookInfo.name === hookName && hookInfo.currentIndex >= i @@ -80,14 +86,20 @@ function createAddHook( hooks ) { } ); } else { // This is the first hook of its type. - hooks[ hookName ] = { + hooksStore[ hookName ] = { handlers: [ handler ], runs: 0, }; } if ( hookName !== 'hookAdded' ) { - doAction( 'hookAdded', hookName, namespace, callback, priority ); + hooks.doAction( + 'hookAdded', + hookName, + namespace, + callback, + priority + ); } }; } diff --git a/packages/hooks/src/createCurrentHook.js b/packages/hooks/src/createCurrentHook.js index c36cab8b43df24..a5565dc98e2cd8 100644 --- a/packages/hooks/src/createCurrentHook.js +++ b/packages/hooks/src/createCurrentHook.js @@ -3,24 +3,19 @@ * currently running hook, or `null` if no hook of the given type is currently * running. * - * @param {Object} hooks Stored hooks, keyed by hook name. + * @param {import('.').Hooks} hooks Hooks instance. + * @param {import('.').StoreKey} storeKey * - * @return {Function} Function that returns the current hook. + * @return {() => string | null} Function that returns the current hook name or null. */ -function createCurrentHook( hooks ) { - /** - * Returns the name of the currently running hook, or `null` if no hook of - * the given type is currently running. - * - * @return {?string} The name of the currently running hook, or - * `null` if no hook is currently running. - */ +function createCurrentHook( hooks, storeKey ) { return function currentHook() { - if ( ! hooks.__current || ! hooks.__current.length ) { - return null; - } + const hooksStore = hooks[ storeKey ]; - return hooks.__current[ hooks.__current.length - 1 ].name; + return ( + hooksStore.__current[ hooksStore.__current.length - 1 ]?.name ?? + null + ); }; } diff --git a/packages/hooks/src/createDidHook.js b/packages/hooks/src/createDidHook.js index 9075507b3d8653..c52aafbe656355 100644 --- a/packages/hooks/src/createDidHook.js +++ b/packages/hooks/src/createDidHook.js @@ -3,29 +3,35 @@ */ import validateHookName from './validateHookName.js'; +/** + * @callback DidHook + * + * Returns the number of times an action has been fired. + * + * @param {string} hookName The hook name to check. + * + * @return {number | undefined} The number of times the hook has run. + */ + /** * Returns a function which, when invoked, will return the number of times a * hook has been called. * - * @param {Object} hooks Stored hooks, keyed by hook name. + * @param {import('.').Hooks} hooks Hooks instance. + * @param {import('.').StoreKey} storeKey * - * @return {Function} Function that returns a hook's call count. + * @return {DidHook} Function that returns a hook's call count. */ -function createDidHook( hooks ) { - /** - * Returns the number of times an action has been fired. - * - * @param {string} hookName The hook name to check. - * - * @return {number} The number of times the hook has run. - */ +function createDidHook( hooks, storeKey ) { return function didHook( hookName ) { + const hooksStore = hooks[ storeKey ]; + if ( ! validateHookName( hookName ) ) { return; } - return hooks[ hookName ] && hooks[ hookName ].runs - ? hooks[ hookName ].runs + return hooksStore[ hookName ] && hooksStore[ hookName ].runs + ? hooksStore[ hookName ].runs : 0; }; } diff --git a/packages/hooks/src/createDoingHook.js b/packages/hooks/src/createDoingHook.js index 055e99e52cedd9..52afbce3ba497c 100644 --- a/packages/hooks/src/createDoingHook.js +++ b/packages/hooks/src/createDoingHook.js @@ -1,30 +1,35 @@ +/** + * @callback DoingHook + * Returns whether a hook is currently being executed. + * + * @param {string} [hookName] The name of the hook to check for. If + * omitted, will check for any hook being executed. + * + * @return {boolean} Whether the hook is being executed. + */ + /** * Returns a function which, when invoked, will return whether a hook is * currently being executed. * - * @param {Object} hooks Stored hooks, keyed by hook name. + * @param {import('.').Hooks} hooks Hooks instance. + * @param {import('.').StoreKey} storeKey * - * @return {Function} Function that returns whether a hook is currently - * being executed. + * @return {DoingHook} Function that returns whether a hook is currently + * being executed. */ -function createDoingHook( hooks ) { - /** - * Returns whether a hook is currently being executed. - * - * @param {?string} hookName The name of the hook to check for. If - * omitted, will check for any hook being executed. - * - * @return {boolean} Whether the hook is being executed. - */ +function createDoingHook( hooks, storeKey ) { return function doingHook( hookName ) { + const hooksStore = hooks[ storeKey ]; + // If the hookName was not passed, check for any current hook. if ( 'undefined' === typeof hookName ) { - return 'undefined' !== typeof hooks.__current[ 0 ]; + return 'undefined' !== typeof hooksStore.__current[ 0 ]; } // Return the __current hook. - return hooks.__current[ 0 ] - ? hookName === hooks.__current[ 0 ].name + return hooksStore.__current[ 0 ] + ? hookName === hooksStore.__current[ 0 ].name : false; }; } diff --git a/packages/hooks/src/createHasHook.js b/packages/hooks/src/createHasHook.js index d92f884560b5e6..36a8c0450626e3 100644 --- a/packages/hooks/src/createHasHook.js +++ b/packages/hooks/src/createHasHook.js @@ -1,34 +1,39 @@ +/** + * @callback HasHook + * + * Returns whether any handlers are attached for the given hookName and optional namespace. + * + * @param {string} hookName The name of the hook to check for. + * @param {string} [namespace] Optional. The unique namespace identifying the callback + * in the form `vendor/plugin/function`. + * + * @return {boolean} Whether there are handlers that are attached to the given hook. + */ /** * Returns a function which, when invoked, will return whether any handlers are * attached to a particular hook. * - * @param {Object} hooks Stored hooks, keyed by hook name. + * @param {import('.').Hooks} hooks Hooks instance. + * @param {import('.').StoreKey} storeKey * - * @return {Function} Function that returns whether any handlers are - * attached to a particular hook and optional namespace. + * @return {HasHook} Function that returns whether any handlers are + * attached to a particular hook and optional namespace. */ -function createHasHook( hooks ) { - /** - * Returns whether any handlers are attached for the given hookName and optional namespace. - * - * @param {string} hookName The name of the hook to check for. - * @param {?string} namespace Optional. The unique namespace identifying the callback - * in the form `vendor/plugin/function`. - * - * @return {boolean} Whether there are handlers that are attached to the given hook. - */ +function createHasHook( hooks, storeKey ) { return function hasHook( hookName, namespace ) { + const hooksStore = hooks[ storeKey ]; + // Use the namespace if provided. if ( 'undefined' !== typeof namespace ) { return ( - hookName in hooks && - hooks[ hookName ].handlers.some( + hookName in hooksStore && + hooksStore[ hookName ].handlers.some( ( hook ) => hook.namespace === namespace ) ); } - return hookName in hooks; + return hookName in hooksStore; }; } diff --git a/packages/hooks/src/createHooks.js b/packages/hooks/src/createHooks.js index d61effc36a4ac0..361383a3a97fc9 100644 --- a/packages/hooks/src/createHooks.js +++ b/packages/hooks/src/createHooks.js @@ -9,37 +9,51 @@ import createCurrentHook from './createCurrentHook'; import createDoingHook from './createDoingHook'; import createDidHook from './createDidHook'; +/** + * Internal class for constructing hooks. Use `createHooks()` function + * + * Note, it is necessary to expose this class to make its type public. + * + * @private + */ +export class _Hooks { + constructor() { + /** @type {import('.').Store} actions */ + this.actions = Object.create( null ); + this.actions.__current = []; + + /** @type {import('.').Store} filters */ + this.filters = Object.create( null ); + this.filters.__current = []; + + this.addAction = createAddHook( this, 'actions' ); + this.addFilter = createAddHook( this, 'filters' ); + this.removeAction = createRemoveHook( this, 'actions' ); + this.removeFilter = createRemoveHook( this, 'filters' ); + this.hasAction = createHasHook( this, 'actions' ); + this.hasFilter = createHasHook( this, 'filters' ); + this.removeAllActions = createRemoveHook( this, 'actions', true ); + this.removeAllFilters = createRemoveHook( this, 'filters', true ); + this.doAction = createRunHook( this, 'actions' ); + this.applyFilters = createRunHook( this, 'filters', true ); + this.currentAction = createCurrentHook( this, 'actions' ); + this.currentFilter = createCurrentHook( this, 'filters' ); + this.doingAction = createDoingHook( this, 'actions' ); + this.doingFilter = createDoingHook( this, 'filters' ); + this.didAction = createDidHook( this, 'actions' ); + this.didFilter = createDidHook( this, 'filters' ); + } +} + +/** @typedef {_Hooks} Hooks */ + /** * Returns an instance of the hooks object. * - * @return {Object} Object that contains all hooks. + * @return {Hooks} A Hooks instance. */ function createHooks() { - const actions = Object.create( null ); - const filters = Object.create( null ); - actions.__current = []; - filters.__current = []; - - return { - addAction: createAddHook( actions ), - addFilter: createAddHook( filters ), - removeAction: createRemoveHook( actions ), - removeFilter: createRemoveHook( filters ), - hasAction: createHasHook( actions ), - hasFilter: createHasHook( filters ), - removeAllActions: createRemoveHook( actions, true ), - removeAllFilters: createRemoveHook( filters, true ), - doAction: createRunHook( actions ), - applyFilters: createRunHook( filters, true ), - currentAction: createCurrentHook( actions ), - currentFilter: createCurrentHook( filters ), - doingAction: createDoingHook( actions ), - doingFilter: createDoingHook( filters ), - didAction: createDidHook( actions ), - didFilter: createDidHook( filters ), - actions, - filters, - }; + return new _Hooks(); } export default createHooks; diff --git a/packages/hooks/src/createRemoveHook.js b/packages/hooks/src/createRemoveHook.js index f2cf9456d45f8a..0b649310f1d599 100644 --- a/packages/hooks/src/createRemoveHook.js +++ b/packages/hooks/src/createRemoveHook.js @@ -3,28 +3,35 @@ */ import validateNamespace from './validateNamespace.js'; import validateHookName from './validateHookName.js'; -import { doAction } from './'; + +/** + * @callback RemoveHook + * Removes the specified callback (or all callbacks) from the hook with a given hookName + * and namespace. + * + * @param {string} hookName The name of the hook to modify. + * @param {string} namespace The unique namespace identifying the callback in the + * form `vendor/plugin/function`. + * + * @return {number | undefined} The number of callbacks removed. + */ /** * Returns a function which, when invoked, will remove a specified hook or all * hooks by the given name. * - * @param {Object} hooks Stored hooks, keyed by hook name. - * @param {boolean} removeAll Whether to remove all callbacks for a hookName, without regard to namespace. Used to create `removeAll*` functions. + * @param {import('.').Hooks} hooks Hooks instance. + * @param {import('.').StoreKey} storeKey + * @param {boolean} [removeAll=false] Whether to remove all callbacks for a hookName, + * without regard to namespace. Used to create + * `removeAll*` functions. * - * @return {Function} Function that removes hooks. + * @return {RemoveHook} Function that removes hooks. */ -function createRemoveHook( hooks, removeAll ) { - /** - * Removes the specified callback (or all callbacks) from the hook with a - * given hookName and namespace. - * - * @param {string} hookName The name of the hook to modify. - * @param {string} namespace The unique namespace identifying the callback in the form `vendor/plugin/function`. - * - * @return {number} The number of callbacks removed. - */ +function createRemoveHook( hooks, storeKey, removeAll = false ) { return function removeHook( hookName, namespace ) { + const hooksStore = hooks[ storeKey ]; + if ( ! validateHookName( hookName ) ) { return; } @@ -34,21 +41,21 @@ function createRemoveHook( hooks, removeAll ) { } // Bail if no hooks exist by this name - if ( ! hooks[ hookName ] ) { + if ( ! hooksStore[ hookName ] ) { return 0; } let handlersRemoved = 0; if ( removeAll ) { - handlersRemoved = hooks[ hookName ].handlers.length; - hooks[ hookName ] = { - runs: hooks[ hookName ].runs, + handlersRemoved = hooksStore[ hookName ].handlers.length; + hooksStore[ hookName ] = { + runs: hooksStore[ hookName ].runs, handlers: [], }; } else { // Try to find the specified callback to remove. - const handlers = hooks[ hookName ].handlers; + const handlers = hooksStore[ hookName ].handlers; for ( let i = handlers.length - 1; i >= 0; i-- ) { if ( handlers[ i ].namespace === namespace ) { handlers.splice( i, 1 ); @@ -58,7 +65,7 @@ function createRemoveHook( hooks, removeAll ) { // comes after the current callback, there's no problem; // otherwise we need to decrease the execution index of any // other runs by 1 to account for the removed element. - ( hooks.__current || [] ).forEach( ( hookInfo ) => { + hooksStore.__current.forEach( ( hookInfo ) => { if ( hookInfo.name === hookName && hookInfo.currentIndex >= i @@ -69,8 +76,9 @@ function createRemoveHook( hooks, removeAll ) { } } } + if ( hookName !== 'hookRemoved' ) { - doAction( 'hookRemoved', hookName, namespace ); + hooks.doAction( 'hookRemoved', hookName, namespace ); } return handlersRemoved; diff --git a/packages/hooks/src/createRunHook.js b/packages/hooks/src/createRunHook.js index 311b1cc9052620..da0e4308713422 100644 --- a/packages/hooks/src/createRunHook.js +++ b/packages/hooks/src/createRunHook.js @@ -3,38 +3,33 @@ * registered to a hook of the specified type, optionally returning the final * value of the call chain. * - * @param {Object} hooks Stored hooks, keyed by hook name. - * @param {?boolean} returnFirstArg Whether each hook callback is expected to - * return its first argument. + * @param {import('.').Hooks} hooks Hooks instance. + * @param {import('.').StoreKey} storeKey + * @param {boolean} [returnFirstArg=false] Whether each hook callback is expected to + * return its first argument. * - * @return {Function} Function that runs hook callbacks. + * @return {(hookName:string, ...args: unknown[]) => unknown} Function that runs hook callbacks. */ -function createRunHook( hooks, returnFirstArg ) { - /** - * Runs all callbacks for the specified hook. - * - * @param {string} hookName The name of the hook to run. - * @param {...*} args Arguments to pass to the hook callbacks. - * - * @return {*} Return value of runner, if applicable. - */ +function createRunHook( hooks, storeKey, returnFirstArg = false ) { return function runHooks( hookName, ...args ) { - if ( ! hooks[ hookName ] ) { - hooks[ hookName ] = { + const hooksStore = hooks[ storeKey ]; + + if ( ! hooksStore[ hookName ] ) { + hooksStore[ hookName ] = { handlers: [], runs: 0, }; } - hooks[ hookName ].runs++; + hooksStore[ hookName ].runs++; - const handlers = hooks[ hookName ].handlers; + const handlers = hooksStore[ hookName ].handlers; // The following code is stripped from production builds. if ( 'production' !== process.env.NODE_ENV ) { // Handle any 'all' hooks registered. - if ( 'hookAdded' !== hookName && hooks.all ) { - handlers.push( ...hooks.all.handlers ); + if ( 'hookAdded' !== hookName && hooksStore.all ) { + handlers.push( ...hooksStore.all.handlers ); } } @@ -47,7 +42,7 @@ function createRunHook( hooks, returnFirstArg ) { currentIndex: 0, }; - hooks.__current.push( hookInfo ); + hooksStore.__current.push( hookInfo ); while ( hookInfo.currentIndex < handlers.length ) { const handler = handlers[ hookInfo.currentIndex ]; @@ -60,7 +55,7 @@ function createRunHook( hooks, returnFirstArg ) { hookInfo.currentIndex++; } - hooks.__current.pop(); + hooksStore.__current.pop(); if ( returnFirstArg ) { return args[ 0 ]; diff --git a/packages/hooks/src/index.js b/packages/hooks/src/index.js index 78a68df8090862..68059d3e8fc66e 100644 --- a/packages/hooks/src/index.js +++ b/packages/hooks/src/index.js @@ -3,6 +3,39 @@ */ import createHooks from './createHooks'; +/** @typedef {(...args: any[])=>any} Callback */ + +/** + * @typedef Handler + * @property {Callback} callback The callback + * @property {string} namespace The namespace + * @property {number} priority The namespace + */ + +/** + * @typedef Hook + * @property {Handler[]} handlers Array of handlers + * @property {number} runs Run counter + */ + +/** + * @typedef Current + * @property {string} name Hook name + * @property {number} currentIndex The index + */ + +/** + * @typedef {Record<string, Hook> & {__current: Current[]}} Store + */ + +/** + * @typedef {'actions' | 'filters'} StoreKey + */ + +/** + * @typedef {import('./createHooks').Hooks} Hooks + */ + const { addAction, addFilter, diff --git a/packages/hooks/src/test/index.test.js b/packages/hooks/src/test/index.test.js index c428c1cd59dc61..da86cc5e56f213 100644 --- a/packages/hooks/src/test/index.test.js +++ b/packages/hooks/src/test/index.test.js @@ -742,6 +742,23 @@ test( 'adding an action triggers a hookAdded action passing all callback details actionA, 9 ); + + // Private instance. + const hooksPrivateInstance = createHooks(); + + removeAction( 'hookAdded', 'my_callback' ); + hookAddedSpy.mockClear(); + + hooksPrivateInstance.addAction( 'hookAdded', 'my_callback', hookAddedSpy ); + hooksPrivateInstance.addAction( 'testAction', 'my_callback2', actionA, 9 ); + + expect( hookAddedSpy ).toHaveBeenCalledTimes( 1 ); + expect( hookAddedSpy ).toHaveBeenCalledWith( + 'testAction', + 'my_callback2', + actionA, + 9 + ); } ); test( 'adding a filter triggers a hookAdded action passing all callback details', () => { @@ -757,6 +774,23 @@ test( 'adding a filter triggers a hookAdded action passing all callback details' filterA, 8 ); + + // Private instance. + const hooksPrivateInstance = createHooks(); + + removeAction( 'hookAdded', 'my_callback' ); + hookAddedSpy.mockClear(); + + hooksPrivateInstance.addAction( 'hookAdded', 'my_callback', hookAddedSpy ); + hooksPrivateInstance.addFilter( 'testFilter', 'my_callback3', filterA, 8 ); + + expect( hookAddedSpy ).toHaveBeenCalledTimes( 1 ); + expect( hookAddedSpy ).toHaveBeenCalledWith( + 'testFilter', + 'my_callback3', + filterA, + 8 + ); } ); test( 'removing an action triggers a hookRemoved action passing all callback details', () => { @@ -772,6 +806,27 @@ test( 'removing an action triggers a hookRemoved action passing all callback det 'testAction', 'my_callback2' ); + + // Private instance. + const hooksPrivateInstance = createHooks(); + + removeAction( 'hookRemoved', 'my_callback' ); + hookRemovedSpy.mockClear(); + + hooksPrivateInstance.addAction( + 'hookRemoved', + 'my_callback', + hookRemovedSpy + ); + + hooksPrivateInstance.addAction( 'testAction', 'my_callback2', actionA, 9 ); + hooksPrivateInstance.removeAction( 'testAction', 'my_callback2' ); + + expect( hookRemovedSpy ).toHaveBeenCalledTimes( 1 ); + expect( hookRemovedSpy ).toHaveBeenCalledWith( + 'testAction', + 'my_callback2' + ); } ); test( 'removing a filter triggers a hookRemoved action passing all callback details', () => { @@ -787,6 +842,27 @@ test( 'removing a filter triggers a hookRemoved action passing all callback deta 'testFilter', 'my_callback3' ); + + // Private instance. + const hooksPrivateInstance = createHooks(); + + removeAction( 'hookRemoved', 'my_callback' ); + hookRemovedSpy.mockClear(); + + hooksPrivateInstance.addAction( + 'hookRemoved', + 'my_callback', + hookRemovedSpy + ); + + hooksPrivateInstance.addFilter( 'testFilter', 'my_callback3', filterA, 8 ); + hooksPrivateInstance.removeFilter( 'testFilter', 'my_callback3' ); + + expect( hookRemovedSpy ).toHaveBeenCalledTimes( 1 ); + expect( hookRemovedSpy ).toHaveBeenCalledWith( + 'testFilter', + 'my_callback3' + ); } ); test( 'add an all filter and run it any hook to trigger it', () => { diff --git a/packages/hooks/tsconfig.json b/packages/hooks/tsconfig.json new file mode 100644 index 00000000000000..671d4a5eba4403 --- /dev/null +++ b/packages/hooks/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "declarationDir": "build-types", + "types": [ "gutenberg-env" ] + }, + "include": [ "src/**/*" ] +} diff --git a/packages/html-entities/package.json b/packages/html-entities/package.json index f8740f94784034..87b0f94e6ffce0 100644 --- a/packages/html-entities/package.json +++ b/packages/html-entities/package.json @@ -24,7 +24,7 @@ "react-native": "src/index", "types": "build-types", "dependencies": { - "@babel/runtime": "^7.11.2" + "@babel/runtime": "^7.12.5" }, "publishConfig": { "access": "public" diff --git a/packages/i18n/CHANGELOG.md b/packages/i18n/CHANGELOG.md index 3c58547c435d91..a9632e02d581d5 100644 --- a/packages/i18n/CHANGELOG.md +++ b/packages/i18n/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Enhancements + +- Improve type declarations for translation functions ([#26171](https://github.com/WordPress/gutenberg/pull/26171)) + ## 3.12.0 (2020-04-30) ### Bug Fix diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 3ab57d62b2b8c4..1c3f0b6b8fb772 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -26,7 +26,7 @@ "pot-to-php": "./tools/pot-to-php.js" }, "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "gettext-parser": "^1.3.1", "lodash": "^4.17.19", "memize": "^1.1.0", diff --git a/packages/i18n/src/create-i18n.js b/packages/i18n/src/create-i18n.js index 0b0b4cd1826d14..0175a947d6f4d5 100644 --- a/packages/i18n/src/create-i18n.js +++ b/packages/i18n/src/create-i18n.js @@ -22,19 +22,69 @@ const DEFAULT_LOCALE_DATA = { }, }; +/* eslint-disable jsdoc/valid-types */ +/** + * @typedef {(data?: LocaleData, domain?: string) => void} SetLocaleData + * Merges locale data into the Tannin instance by domain. Accepts data in a + * Jed-formatted JSON object shape. + * + * @see http://messageformat.github.io/Jed/ + */ +/** + * @typedef {(text: string, domain?: string) => string} __ + * + * Retrieve the translation of text. + * + * @see https://developer.wordpress.org/reference/functions/__/ + */ +/** + * @typedef {(text: string, context: string, domain?: string) => string} _x + * + * Retrieve translated string with gettext context. + * + * @see https://developer.wordpress.org/reference/functions/_x/ + */ +/** + * @typedef {(single: string, plural: string, number: number, domain?: string) => string} _n + * + * Translates and retrieves the singular or plural form based on the supplied + * number. + * + * @see https://developer.wordpress.org/reference/functions/_n/ + */ +/** + * @typedef {(single: string, plural: string, number: number, context: string, domain?: string) => string} _nx + * + * Translates and retrieves the singular or plural form based on the supplied + * number, with gettext context. + * + * @see https://developer.wordpress.org/reference/functions/_nx/ + */ +/** + * @typedef {() => boolean} IsRtl + * + * Check if current locale is RTL. + * + * **RTL (Right To Left)** is a locale property indicating that text is written from right to left. + * For example, the `he` locale (for Hebrew) specifies right-to-left. Arabic (ar) is another common + * language written RTL. The opposite of RTL, LTR (Left To Right) is used in other languages, + * including English (`en`, `en-US`, `en-GB`, etc.), Spanish (`es`), and French (`fr`). + */ +/* eslint-enable jsdoc/valid-types */ + /** * An i18n instance * - * @typedef {Object} I18n - * @property {Function} setLocaleData Merges locale data into the Tannin instance by domain. Accepts data in a - * Jed-formatted JSON object shape. - * @property {Function} __ Retrieve the translation of text. - * @property {Function} _x Retrieve translated string with gettext context. - * @property {Function} _n Translates and retrieves the singular or plural form based on the supplied - * number. - * @property {Function} _nx Translates and retrieves the singular or plural form based on the supplied - * number, with gettext context. - * @property {Function} isRTL Check if current locale is RTL. + * @typedef I18n + * @property {SetLocaleData} setLocaleData Merges locale data into the Tannin instance by domain. Accepts data in a + * Jed-formatted JSON object shape. + * @property {__} __ Retrieve the translation of text. + * @property {_x} _x Retrieve translated string with gettext context. + * @property {_n} _n Translates and retrieves the singular or plural form based on the supplied + * number. + * @property {_nx} _nx Translates and retrieves the singular or plural form based on the supplied + * number, with gettext context. + * @property {IsRtl} isRTL Check if current locale is RTL. */ /** @@ -52,15 +102,7 @@ export const createI18n = ( initialData, initialDomain ) => { */ const tannin = new Tannin( {} ); - /** - * Merges locale data into the Tannin instance by domain. Accepts data in a - * Jed-formatted JSON object shape. - * - * @see http://messageformat.github.io/Jed/ - * - * @param {LocaleData} [data] Locale data configuration. - * @param {string} [domain] Domain for which configuration applies. - */ + /** @type {SetLocaleData} */ const setLocaleData = ( data, domain = 'default' ) => { tannin.data[ domain ] = { ...DEFAULT_LOCALE_DATA, @@ -105,82 +147,27 @@ export const createI18n = ( initialData, initialDomain ) => { return tannin.dcnpgettext( domain, context, single, plural, number ); }; - /** - * Retrieve the translation of text. - * - * @see https://developer.wordpress.org/reference/functions/__/ - * - * @param {string} text Text to translate. - * @param {string} [domain] Domain to retrieve the translated text. - * - * @return {string} Translated text. - */ + /** @type {__} */ const __ = ( text, domain ) => { return dcnpgettext( domain, undefined, text ); }; - /** - * Retrieve translated string with gettext context. - * - * @see https://developer.wordpress.org/reference/functions/_x/ - * - * @param {string} text Text to translate. - * @param {string} context Context information for the translators. - * @param {string} [domain] Domain to retrieve the translated text. - * - * @return {string} Translated context string without pipe. - */ + /** @type {_x} */ const _x = ( text, context, domain ) => { return dcnpgettext( domain, context, text ); }; - /** - * Translates and retrieves the singular or plural form based on the supplied - * number. - * - * @see https://developer.wordpress.org/reference/functions/_n/ - * - * @param {string} single The text to be used if the number is singular. - * @param {string} plural The text to be used if the number is plural. - * @param {number} number The number to compare against to use either the - * singular or plural form. - * @param {string} [domain] Domain to retrieve the translated text. - * - * @return {string} The translated singular or plural form. - */ + /** @type {_n} */ const _n = ( single, plural, number, domain ) => { return dcnpgettext( domain, undefined, single, plural, number ); }; - /** - * Translates and retrieves the singular or plural form based on the supplied - * number, with gettext context. - * - * @see https://developer.wordpress.org/reference/functions/_nx/ - * - * @param {string} single The text to be used if the number is singular. - * @param {string} plural The text to be used if the number is plural. - * @param {number} number The number to compare against to use either the - * singular or plural form. - * @param {string} context Context information for the translators. - * @param {string} [domain] Domain to retrieve the translated text. - * - * @return {string} The translated singular or plural form. - */ + /** @type {_nx} */ const _nx = ( single, plural, number, context, domain ) => { return dcnpgettext( domain, context, single, plural, number ); }; - /** - * Check if current locale is RTL. - * - * **RTL (Right To Left)** is a locale property indicating that text is written from right to left. - * For example, the `he` locale (for Hebrew) specifies right-to-left. Arabic (ar) is another common - * language written RTL. The opposite of RTL, LTR (Left To Right) is used in other languages, - * including English (`en`, `en-US`, `en-GB`, etc.), Spanish (`es`), and French (`fr`). - * - * @return {boolean} Whether locale is RTL. - */ + /** @type {IsRtl} */ const isRTL = () => { return 'rtl' === _x( 'ltr', 'text direction' ); }; diff --git a/packages/icons/package.json b/packages/icons/package.json index a4234a349e499f..fe885fe37885fa 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -25,7 +25,7 @@ "react-native": "src/index", "types": "build-types", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/element": "file:../element", "@wordpress/primitives": "file:../primitives" }, diff --git a/packages/icons/src/index.js b/packages/icons/src/index.js index bdfbc5bbadabcb..7140f8a9a3d94d 100644 --- a/packages/icons/src/index.js +++ b/packages/icons/src/index.js @@ -60,6 +60,7 @@ export { default as file } from './library/file'; export { default as flipHorizontal } from './library/flip-horizontal'; export { default as flipVertical } from './library/flip-vertical'; export { default as formatBold } from './library/format-bold'; +export { default as formatCapitalize } from './library/format-capitalize'; export { default as formatIndent } from './library/format-indent'; export { default as formatIndentRTL } from './library/format-indent-rtl'; export { default as formatItalic } from './library/format-italic'; @@ -68,10 +69,13 @@ export { default as formatListBulletsRTL } from './library/format-list-bullets-r export { default as formatListNumbered } from './library/format-list-numbered'; export { default as formatListNumberedRTL } from './library/format-list-numbered-rtl'; export { default as formatLtr } from './library/format-ltr'; +export { default as formatLowercase } from './library/format-lowercase'; export { default as formatOutdent } from './library/format-outdent'; export { default as formatOutdentRTL } from './library/format-outdent-rtl'; export { default as formatRtl } from './library/format-rtl'; export { default as formatStrikethrough } from './library/format-strikethrough'; +export { default as formatUnderline } from './library/format-underline'; +export { default as formatUppercase } from './library/format-uppercase'; export { default as fullscreen } from './library/fullscreen'; export { default as gallery } from './library/gallery'; export { default as globe } from './library/globe'; @@ -146,6 +150,7 @@ export { default as rotateRight } from './library/rotate-right'; export { default as rss } from './library/rss'; export { default as search } from './library/search'; export { default as separator } from './library/separator'; +export { default as settings } from './library/settings'; export { default as share } from './library/share'; export { default as shortcode } from './library/shortcode'; export { default as stack } from './library/stack'; @@ -166,6 +171,9 @@ export { default as tableRowBefore } from './library/table-row-before'; export { default as tableRowDelete } from './library/table-row-delete'; export { default as table } from './library/table'; export { default as tag } from './library/tag'; +export { default as templatePartFooter } from './library/template-part-footer'; +export { default as templatePartHeader } from './library/template-part-header'; +export { default as templatePartSidebar } from './library/template-part-sidebar'; export { default as textColor } from './library/text-color'; export { default as tablet } from './library/tablet'; export { default as title } from './library/title'; diff --git a/packages/icons/src/library/backup.js b/packages/icons/src/library/backup.js index 3a50f31efd6a76..39b0b541bc2980 100644 --- a/packages/icons/src/library/backup.js +++ b/packages/icons/src/library/backup.js @@ -4,8 +4,8 @@ import { SVG, Path } from '@wordpress/primitives'; const backup = ( - <SVG xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24"> - <Path d="M13.65 2.88c3.93 2.01 5.48 6.84 3.47 10.77s-6.83 5.48-10.77 3.47c-1.87-.96-3.2-2.56-3.86-4.4l1.64-1.03c.45 1.57 1.52 2.95 3.08 3.76 3.01 1.54 6.69.35 8.23-2.66 1.55-3.01.36-6.69-2.65-8.24C9.78 3.01 6.1 4.2 4.56 7.21l1.88.97-4.95 3.08-.39-5.82 1.78.91C4.9 2.4 9.75.89 13.65 2.88zm-4.36 7.83C9.11 10.53 9 10.28 9 10c0-.07.03-.12.04-.19h-.01L10 5l.97 4.81L14 13l-4.5-2.12.02-.02c-.08-.04-.16-.09-.23-.15z" /> + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M5.5 12h1.75l-2.5 3-2.5-3H4a8 8 0 113.134 6.35l.907-1.194A6.5 6.5 0 105.5 12zm9.53 1.97l-2.28-2.28V8.5a.75.75 0 00-1.5 0V12a.747.747 0 00.218.529l1.282-.84-1.28.842 2.5 2.5a.75.75 0 101.06-1.061z" /> </SVG> ); diff --git a/packages/icons/src/library/format-capitalize.js b/packages/icons/src/library/format-capitalize.js new file mode 100644 index 00000000000000..a41b335830bc4d --- /dev/null +++ b/packages/icons/src/library/format-capitalize.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const formatCapitalize = ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M7.1 6.8L3.1 18h1.6l1.1-3h4.3l1.1 3h1.6l-4-11.2H7.1zm-.8 6.8L8 8.9l1.7 4.7H6.3zm14.5-1.5c-.3-.6-.7-1.1-1.2-1.5-.6-.4-1.2-.6-1.9-.6-.5 0-.9.1-1.4.3-.4.2-.8.5-1.1.8V6h-1.4v12h1.3l.2-1c.2.4.6.6 1 .8.4.2.9.3 1.4.3.7 0 1.2-.2 1.8-.5.5-.4 1-.9 1.3-1.5.3-.6.5-1.3.5-2.1-.1-.6-.2-1.3-.5-1.9zm-1.7 4c-.4.5-.9.8-1.6.8s-1.2-.2-1.7-.7c-.4-.5-.7-1.2-.7-2.1 0-.9.2-1.6.7-2.1.4-.5 1-.7 1.7-.7s1.2.3 1.6.8c.4.5.6 1.2.6 2 .1.8-.2 1.4-.6 2z" /> + </SVG> +); + +export default formatCapitalize; diff --git a/packages/icons/src/library/format-lowercase.js b/packages/icons/src/library/format-lowercase.js new file mode 100644 index 00000000000000..04278051c0bf0a --- /dev/null +++ b/packages/icons/src/library/format-lowercase.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const formatLowercase = ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M10.8 16.8c-.1-.1-.2-.3-.3-.5v-2.6c0-.9-.1-1.7-.3-2.2-.2-.5-.5-.9-.9-1.1-.4-.3-.9-.4-1.6-.4-.5 0-1 .1-1.5.2s-.9.3-1.2.6l.3 1.2c.4-.3.7-.4 1.1-.5.3-.1.7-.2 1-.2.6 0 1 .1 1.3.4.3.2.4.7.4 1.4-1.2 0-2.3.2-3.3.7s-1.4 1.1-1.4 2.1c0 .7.2 1.2.7 1.6.4.4 1 .6 1.8.6.9 0 1.7-.4 2.4-1.2.1.3.2.5.4.7.1.2.3.3.6.4.3.1.6.1 1.1.1h.1l.2-1.2h-.1c-.5.1-.7 0-.8-.1zM9.1 16c-.2.3-.5.6-.9.8-.4.1-.7.2-1.1.2-.4 0-.7-.1-.9-.3-.2-.2-.3-.5-.3-.9 0-.6.2-1 .7-1.3.5-.3 1.3-.4 2.5-.5v2zm10.5-3.9c-.3-.6-.7-1.1-1.2-1.5-.5-.4-1.2-.6-1.9-.6-.5 0-.9.1-1.4.3-.4.2-.8.5-1.1.8V6h-1.4v12h1.3l.2-1c.2.4.6.6 1 .8.4.2.9.3 1.4.3.7 0 1.2-.2 1.8-.5.5-.4 1-.9 1.3-1.5.3-.6.5-1.3.5-2.1 0-.6-.2-1.3-.5-1.9zm-1.6 4c-.4.5-.9.8-1.6.8s-1.2-.2-1.7-.7c-.5-.5-.7-1.2-.7-2.1 0-.9.2-1.6.7-2.1.4-.5 1-.7 1.7-.7s1.2.3 1.6.8c.4.5.6 1.2.6 2s-.2 1.4-.6 2z" /> + </SVG> +); + +export default formatLowercase; diff --git a/packages/icons/src/library/format-underline.js b/packages/icons/src/library/format-underline.js new file mode 100644 index 00000000000000..cbfbe9c865579a --- /dev/null +++ b/packages/icons/src/library/format-underline.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const formatUnderline = ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M7 18v1h10v-1H7zm5-2c1.5 0 2.6-.4 3.4-1.2.8-.8 1.1-2 1.1-3.5V5H15v5.8c0 1.2-.2 2.1-.6 2.8-.4.7-1.2 1-2.4 1s-2-.3-2.4-1c-.4-.7-.6-1.6-.6-2.8V5H7.5v6.2c0 1.5.4 2.7 1.1 3.5.8.9 1.9 1.3 3.4 1.3z" /> + </SVG> +); + +export default formatUnderline; diff --git a/packages/icons/src/library/format-uppercase.js b/packages/icons/src/library/format-uppercase.js new file mode 100644 index 00000000000000..d7e3c08a55e1ad --- /dev/null +++ b/packages/icons/src/library/format-uppercase.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const formatUppercase = ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M6.1 6.8L2.1 18h1.6l1.1-3h4.3l1.1 3h1.6l-4-11.2H6.1zm-.8 6.8L7 8.9l1.7 4.7H5.3zm15.1-.7c-.4-.5-.9-.8-1.6-1 .4-.2.7-.5.8-.9.2-.4.3-.9.3-1.4 0-.9-.3-1.6-.8-2-.6-.5-1.3-.7-2.4-.7h-3.5V18h4.2c1.1 0 2-.3 2.6-.8.6-.6 1-1.4 1-2.4-.1-.8-.3-1.4-.6-1.9zm-5.7-4.7h1.8c.6 0 1.1.1 1.4.4.3.2.5.7.5 1.3 0 .6-.2 1.1-.5 1.3-.3.2-.8.4-1.4.4h-1.8V8.2zm4 8c-.4.3-.9.5-1.5.5h-2.6v-3.8h2.6c1.4 0 2 .6 2 1.9.1.6-.1 1-.5 1.4z" /> + </SVG> +); + +export default formatUppercase; diff --git a/packages/icons/src/library/keyboard-return.js b/packages/icons/src/library/keyboard-return.js index 4855d708131fb9..dbced364bac4e4 100644 --- a/packages/icons/src/library/keyboard-return.js +++ b/packages/icons/src/library/keyboard-return.js @@ -5,7 +5,7 @@ import { SVG, Path } from '@wordpress/primitives'; const keyboardReturn = ( <SVG xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24"> - <Path d="M16 4h2v9H7v3l-5-4 5-4v3h9V4z" /> + <Path d="M6.734 16.106l2.176-2.38-1.093-1.028-3.846 4.158 3.846 4.157 1.093-1.027-2.176-2.38h2.811c1.125 0 2.25.03 3.374 0 1.428-.001 3.362-.25 4.963-1.277 1.66-1.065 2.868-2.906 2.868-5.859 0-2.479-1.327-4.896-3.65-5.93-1.82-.813-3.044-.8-4.806-.788l-.567.002v1.5c.184 0 .368 0 .553-.002 1.82-.007 2.704-.014 4.21.657 1.854.827 2.76 2.657 2.76 4.561 0 2.472-.973 3.824-2.178 4.596-1.258.807-2.864 1.04-4.163 1.04h-.02c-1.115.03-2.229 0-3.344 0H6.734z" /> </SVG> ); diff --git a/packages/icons/src/library/settings.js b/packages/icons/src/library/settings.js new file mode 100644 index 00000000000000..58ca3e3e479fcc --- /dev/null +++ b/packages/icons/src/library/settings.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const settings = ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M17 4h-2v4.5h2V7h3V5.5h-3V4zM4 5.5h9V7H4V5.5zm16 5.75h-9v1.5h9v-1.5zm-16 0h3V10h2v4.25H7v-1.5H4v-1.5zM9 17H4v1.5h5V17zm4 0h7v1.5h-7V20h-2v-4.25h2V17z" /> + </SVG> +); + +export default settings; diff --git a/packages/icons/src/library/template-part-footer.js b/packages/icons/src/library/template-part-footer.js new file mode 100644 index 00000000000000..bffc0f19716914 --- /dev/null +++ b/packages/icons/src/library/template-part-footer.js @@ -0,0 +1,16 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const templatePartFooter = ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path + fill-rule="evenodd" + d="M18 5.5h-8v8h8.5V6a.5.5 0 00-.5-.5zm-9.5 8h-3V6a.5.5 0 01.5-.5h2.5v8zM6 4h12a2 2 0 012 2v12a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2z" + clip-rule="evenodd" + /> + </SVG> +); + +export default templatePartFooter; diff --git a/packages/icons/src/library/template-part-header.js b/packages/icons/src/library/template-part-header.js new file mode 100644 index 00000000000000..757eabe4acd323 --- /dev/null +++ b/packages/icons/src/library/template-part-header.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const templatePartHeader = ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M18.5 10.5H10v8h8a.5.5 0 00.5-.5v-7.5zm-10 0h-3V18a.5.5 0 00.5.5h2.5v-8zM6 4h12a2 2 0 012 2v12a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2z" /> + </SVG> +); + +export default templatePartHeader; diff --git a/packages/icons/src/library/template-part-sidebar.js b/packages/icons/src/library/template-part-sidebar.js new file mode 100644 index 00000000000000..4156a163a88ba0 --- /dev/null +++ b/packages/icons/src/library/template-part-sidebar.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const templatePartSidebar = ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M18 5.5H6a.5.5 0 00-.5.5v3h13V6a.5.5 0 00-.5-.5zm.5 5H10v8h8a.5.5 0 00.5-.5v-7.5zM6 4h12a2 2 0 012 2v12a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2z" /> + </SVG> +); + +export default templatePartSidebar; diff --git a/packages/interface/CHANGELOG.md b/packages/interface/CHANGELOG.md index f11e8cd3010869..1522d57767261a 100644 --- a/packages/interface/CHANGELOG.md +++ b/packages/interface/CHANGELOG.md @@ -2,4 +2,14 @@ ## Unreleased +### New Feature + +- Added a store definition `store` for the interface namespace to use with `@wordpress/data` API ([#26655](https://github.com/WordPress/gutenberg/pull/26655)). + +### Deprecations + +- `leftSidebar` prop in `InterfaceSkeleton` component has been deprecated ([#26517](https://github.com/WordPress/gutenberg/pull/26517). Use `secondarySidebar` prop instead. + +## 0.1.0 (2020-04-15) + Initial release. diff --git a/packages/interface/package.json b/packages/interface/package.json index 1da2a3cce93010..62cc58ac48affd 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/interface", - "version": "0.10.4", + "version": "0.10.5", "description": "Interface module for WordPress. The package contains shared functionality across the modern JavaScript-based WordPress screens.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -24,16 +24,19 @@ "react-native": "src/index", "sideEffects": [ "build-style/**", - "!((src|build|build-module)/components/**)" + "src/**/*.scss", + "{src,build,build-module}/{index.js,store/index.js}" ], "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/components": "file:../components", "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", "@wordpress/plugins": "file:../plugins", + "@wordpress/viewport": "file:../viewport", "classnames": "^2.2.5", "lodash": "^4.17.19" }, diff --git a/packages/interface/src/components/complementary-area/index.js b/packages/interface/src/components/complementary-area/index.js index 83520dd3c0df9a..f35fcf9d14bc17 100644 --- a/packages/interface/src/components/complementary-area/index.js +++ b/packages/interface/src/components/complementary-area/index.js @@ -6,11 +6,12 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Animate, Button, Panel, Slot, Fill } from '@wordpress/components'; +import { Button, Panel, Slot, Fill } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { check, starEmpty, starFilled } from '@wordpress/icons'; import { useEffect, useRef } from '@wordpress/element'; +import { store as viewportStore } from '@wordpress/viewport'; /** * Internal dependencies @@ -27,9 +28,7 @@ function ComplementaryAreaSlot( { scope, ...props } ) { function ComplementaryAreaFill( { scope, children, className } ) { return ( <Fill name={ `ComplementaryArea/${ scope }` }> - <Animate type="slide-in" options={ { origin: 'left' } }> - { () => <div className={ className }>{ children }</div> } - </Animate> + <div className={ className }>{ children }</div> </Fill> ); } @@ -106,10 +105,8 @@ function ComplementaryArea( { isActive: _activeArea === identifier, isPinned: isItemPinned( scope, identifier ), activeArea: _activeArea, - isSmall: select( 'core/viewport' ).isViewportMatch( - '< medium' - ), - isLarge: select( 'core/viewport' ).isViewportMatch( 'large' ), + isSmall: select( viewportStore ).isViewportMatch( '< medium' ), + isLarge: select( viewportStore ).isViewportMatch( 'large' ), }; }, [ identifier, scope ] diff --git a/packages/interface/src/components/index.js b/packages/interface/src/components/index.js index d8ba05d8230dc2..971b42522ae3c4 100644 --- a/packages/interface/src/components/index.js +++ b/packages/interface/src/components/index.js @@ -3,5 +3,4 @@ export { default as ComplementaryAreaMoreMenuItem } from './complementary-area-m export { default as FullscreenMode } from './fullscreen-mode'; export { default as InterfaceSkeleton } from './interface-skeleton'; export { default as PinnedItems } from './pinned-items'; -export { default as __experimentalMainDashboardButton } from './main-dashboard-button'; export { default as ActionItem } from './action-item'; diff --git a/packages/interface/src/components/interface-skeleton/index.js b/packages/interface/src/components/interface-skeleton/index.js index 6f2607297e6532..0145c2457e54cf 100644 --- a/packages/interface/src/components/interface-skeleton/index.js +++ b/packages/interface/src/components/interface-skeleton/index.js @@ -6,8 +6,9 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useEffect } from '@wordpress/element'; -import { navigateRegions } from '@wordpress/components'; +import { forwardRef, useEffect, useRef } from '@wordpress/element'; +import { __unstableUseNavigateRegions as useNavigateRegions } from '@wordpress/components'; +import deprecated from '@wordpress/deprecated'; import { __ } from '@wordpress/i18n'; function useHTMLClass( className ) { @@ -24,17 +25,29 @@ function useHTMLClass( className ) { }, [ className ] ); } -function InterfaceSkeleton( { - footer, - header, - sidebar, - leftSidebar, - content, - drawer, - actions, - labels, - className, -} ) { +function InterfaceSkeleton( + { + footer, + header, + sidebar, + secondarySidebar, + content, + drawer, + actions, + labels, + className, + // Deprecated props. + leftSidebar, + shortcuts, + }, + ref +) { + const fallbackRef = useRef(); + + ref = ref || fallbackRef; + + const regionsClassName = useNavigateRegions( ref, shortcuts ); + useHTMLClass( 'interface-interface-skeleton__html-container' ); const defaultLabels = { @@ -44,8 +57,8 @@ function InterfaceSkeleton( { header: __( 'Header' ), /* translators: accessibility text for the content landmark region. */ body: __( 'Content' ), - /* translators: accessibility text for the left sidebar landmark region. */ - leftSidebar: __( 'Left sidebar' ), + /* translators: accessibility text for the secondary sidebar landmark region. */ + secondarySidebar: __( 'Block Library' ), /* translators: accessibility text for the settings landmark region. */ sidebar: __( 'Settings' ), /* translators: accessibility text for the publish landmark region. */ @@ -56,11 +69,22 @@ function InterfaceSkeleton( { const mergedLabels = { ...defaultLabels, ...labels }; + if ( leftSidebar ) { + deprecated( 'leftSidebar prop in InterfaceSkeleton component', { + alternative: 'secondarySidebar prop', + version: '9.7.0', + plugin: 'Gutenberg', + } ); + secondarySidebar = leftSidebar; + } + return ( <div + ref={ ref } className={ classnames( className, - 'interface-interface-skeleton' + 'interface-interface-skeleton', + regionsClassName ) } > { !! drawer && ( @@ -84,14 +108,14 @@ function InterfaceSkeleton( { </div> ) } <div className="interface-interface-skeleton__body"> - { !! leftSidebar && ( + { !! secondarySidebar && ( <div - className="interface-interface-skeleton__left-sidebar" + className="interface-interface-skeleton__secondary-sidebar" role="region" - aria-label={ mergedLabels.leftSidebar } + aria-label={ mergedLabels.secondarySidebar } tabIndex="-1" > - { leftSidebar } + { secondarySidebar } </div> ) } <div @@ -138,4 +162,4 @@ function InterfaceSkeleton( { ); } -export default navigateRegions( InterfaceSkeleton ); +export default forwardRef( InterfaceSkeleton ); diff --git a/packages/interface/src/components/interface-skeleton/style.scss b/packages/interface/src/components/interface-skeleton/style.scss index ca4a23c3f50977..5d10bd9cb03377 100644 --- a/packages/interface/src/components/interface-skeleton/style.scss +++ b/packages/interface/src/components/interface-skeleton/style.scss @@ -61,6 +61,11 @@ html.interface-interface-skeleton__html-container { // it is added, it should take care of the issue. // See also: https://drafts.csswg.org/css-overscroll/ overscroll-behavior-y: none; + + // Footer overlap prevention + @include break-medium() { + padding-bottom: $button-size-small + $border-width; + } } .interface-interface-skeleton__content { @@ -79,10 +84,9 @@ html.interface-interface-skeleton__html-container { } -.interface-interface-skeleton__left-sidebar, +.interface-interface-skeleton__secondary-sidebar, .interface-interface-skeleton__sidebar { display: block; - width: auto; // Keep the sidebar width flexible. flex-shrink: 0; position: absolute; z-index: z-index(".interface-interface-skeleton__sidebar"); @@ -97,6 +101,7 @@ html.interface-interface-skeleton__html-container { @include break-medium() { position: relative !important; z-index: z-index(".interface-interface-skeleton__sidebar {greater than small}"); + width: auto; // Keep the sidebar width flexible. } } @@ -107,7 +112,7 @@ html.interface-interface-skeleton__html-container { } } -.interface-interface-skeleton__left-sidebar { +.interface-interface-skeleton__secondary-sidebar { @include break-medium() { border-right: $border-width solid $gray-200; } @@ -143,13 +148,6 @@ html.interface-interface-skeleton__html-container { background-color: $white; z-index: z-index(".interface-interface-skeleton__footer"); - // When the navigate regions shortcut is used it applies position: relative - // to regions. The footer should always stay stuck to the bottom though, so - // override using a more specific selector with position: fixed. - .is-focusing-regions &[role="region"] { - position: fixed; - } - // On Mobile the footer is hidden display: none; @include break-medium() { diff --git a/packages/interface/src/index.js b/packages/interface/src/index.js index 7b5bd2101b7195..cf6bfc074cc058 100644 --- a/packages/interface/src/index.js +++ b/packages/interface/src/index.js @@ -1,6 +1,2 @@ -/** - * Internal dependencies - */ -import './store'; - +export { store } from './store'; export * from './components'; diff --git a/packages/interface/src/store/constants.js b/packages/interface/src/store/constants.js index ad82e7c6a6d755..766ef5e1c88737 100644 --- a/packages/interface/src/store/constants.js +++ b/packages/interface/src/store/constants.js @@ -3,4 +3,4 @@ * * @type {string} */ -export const STORE_KEY = 'core/interface'; +export const STORE_NAME = 'core/interface'; diff --git a/packages/interface/src/store/index.js b/packages/interface/src/store/index.js index 12c723cd51b04e..6d0337997ae930 100644 --- a/packages/interface/src/store/index.js +++ b/packages/interface/src/store/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { registerStore } from '@wordpress/data'; +import { createReduxStore, registerStore } from '@wordpress/data'; /** * Internal dependencies @@ -9,13 +9,27 @@ import { registerStore } from '@wordpress/data'; import reducer from './reducer'; import * as actions from './actions'; import * as selectors from './selectors'; -import { STORE_KEY } from './constants'; +import { STORE_NAME } from './constants'; -const store = registerStore( STORE_KEY, { +/** + * Store definition for the interface namespace. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore + * + * @type {Object} + */ +export const store = createReduxStore( STORE_NAME, { reducer, actions, selectors, persist: [ 'enableItems' ], } ); -export default store; +// Once we build a more generic persistence plugin that works across types of stores +// we'd be able to replace this with a register call. +registerStore( STORE_NAME, { + reducer, + actions, + selectors, + persist: [ 'enableItems' ], +} ); diff --git a/packages/is-shallow-equal/.eslintrc.json b/packages/is-shallow-equal/.eslintrc.json deleted file mode 100644 index 2e49bacccbbdbd..00000000000000 --- a/packages/is-shallow-equal/.eslintrc.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "root": true, - "extends": [ - "plugin:@wordpress/eslint-plugin/es5" - ], - "env": { - "node": true - }, - "settings": { - "jsdoc": { - "mode": "typescript" - } - }, - "overrides": [ - { - "files": [ "@(test|benchmark)/*.js" ], - "extends": [ - "plugin:@wordpress/eslint-plugin/recommended" - ] - } - ] -} diff --git a/packages/is-shallow-equal/CHANGELOG.md b/packages/is-shallow-equal/CHANGELOG.md index 0501783db3f36e..bdd95fb63468e7 100644 --- a/packages/is-shallow-equal/CHANGELOG.md +++ b/packages/is-shallow-equal/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Change + +- Re-write using ES Modules causing CJS default import to change from `require('@wordpress/is-shallow-equal)` to `require('@wordpress/is-shallow-equal).default`. ([#26833](https://github.com/WordPress/gutenberg/pull/26833)) + ## 2.0.0 (2020-04-15) ### Breaking Change diff --git a/packages/is-shallow-equal/README.md b/packages/is-shallow-equal/README.md index 04421cfa1408f2..95837e95a5c72a 100644 --- a/packages/is-shallow-equal/README.md +++ b/packages/is-shallow-equal/README.md @@ -122,10 +122,10 @@ The following results were produced under Node v10.15.3 (LTS) on a MacBook Pro ( You can run the benchmarks yourselves by cloning the repository, installing dependencies, and running the `benchmark/index.js` script: ``` -git clone https://github.com/WordPress/packages.git -cd packages/packages/is-shallow-equal +git clone https://github.com/WordPress/gutenberg.git npm install -node benchmark +npm run build:packages +node ./packages/is-shallow-equal/benchmark ``` <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/is-shallow-equal/benchmark/index.js b/packages/is-shallow-equal/benchmark/index.js index d734712d67fe92..3564146321c501 100644 --- a/packages/is-shallow-equal/benchmark/index.js +++ b/packages/is-shallow-equal/benchmark/index.js @@ -26,23 +26,8 @@ Promise.all( [ lazyImport( 'shallow-equal@^1.2.1' ), lazyImport( 'is-equal-shallow@^0.1.3' ), lazyImport( 'shallow-equals@^1.0.0' ), - new Promise( async ( resolve ) => { - try { - await lazyImport( 'fbjs@^1.0.0' ); - } catch ( error ) { - // The fjbs package throws an error when imported directly. Since - // lazyImport will automatically require the module for the resolved - // value, anticipate and disregard the error, as long as it's the - // expected error message. - if ( - 'The fbjs package should not be required without a full path.' !== - error.message - ) { - throw error; - } - } - - resolve( require( 'fbjs/lib/shallowEqual' ) ); + lazyImport( 'fbjs@^1.0.0', { + localPath: './lib/shallowEqual', } ), ] ).then( ( [ @@ -58,7 +43,7 @@ Promise.all( [ require( '..' ).isShallowEqualObjects, require( '..' ).isShallowEqualArrays, ], - [ '@wordpress/is-shallow-equal', require( '..' ) ], + [ '@wordpress/is-shallow-equal', require( '..' ).default ], [ 'shallowequal', shallowequal ], [ 'shallow-equal (type specific)', diff --git a/packages/is-shallow-equal/package.json b/packages/is-shallow-equal/package.json index d1cd9c45e52703..a66bd52c037fc9 100644 --- a/packages/is-shallow-equal/package.json +++ b/packages/is-shallow-equal/package.json @@ -21,15 +21,19 @@ "url": "https://github.com/WordPress/gutenberg/issues" }, "files": [ - "lib", + "build", + "build-module", "build-types", + "src", "*.md" ], - "main": "lib/index.js", + "main": "build/index.js", + "module": "build-module/index.js", + "react-native": "src/index", "types": "build-types", "sideEffects": false, "dependencies": { - "@babel/runtime": "^7.11.2" + "@babel/runtime": "^7.12.5" }, "publishConfig": { "access": "public" diff --git a/packages/is-shallow-equal/lib/arrays.js b/packages/is-shallow-equal/src/arrays.js similarity index 73% rename from packages/is-shallow-equal/lib/arrays.js rename to packages/is-shallow-equal/src/arrays.js index 6937373a985467..88adc7617e3bb3 100644 --- a/packages/is-shallow-equal/lib/arrays.js +++ b/packages/is-shallow-equal/src/arrays.js @@ -1,5 +1,3 @@ -'use strict'; - /** * Returns true if the two arrays are shallow equal, or false otherwise. * @@ -8,9 +6,7 @@ * * @return {boolean} Whether the two arrays are shallow equal. */ -function isShallowEqualArrays( a, b ) { - var i; - +export default function isShallowEqualArrays( a, b ) { if ( a === b ) { return true; } @@ -19,7 +15,7 @@ function isShallowEqualArrays( a, b ) { return false; } - for ( i = 0; i < a.length; i++ ) { + for ( let i = 0, len = a.length; i < len; i++ ) { if ( a[ i ] !== b[ i ] ) { return false; } @@ -27,5 +23,3 @@ function isShallowEqualArrays( a, b ) { return true; } - -module.exports = isShallowEqualArrays; diff --git a/packages/is-shallow-equal/lib/index.js b/packages/is-shallow-equal/src/index.js similarity index 58% rename from packages/is-shallow-equal/lib/index.js rename to packages/is-shallow-equal/src/index.js index 3469656d020376..fddd62a022a3de 100644 --- a/packages/is-shallow-equal/lib/index.js +++ b/packages/is-shallow-equal/src/index.js @@ -1,12 +1,11 @@ -'use strict'; - /** - * Internal dependencies; + * Internal dependencies */ -var isShallowEqualObjects = require( './objects' ); -var isShallowEqualArrays = require( './arrays' ); +import isShallowEqualObjects from './objects'; +import isShallowEqualArrays from './arrays'; -var isArray = Array.isArray; +export { default as isShallowEqualObjects } from './objects'; +export { default as isShallowEqualArrays } from './arrays'; /** * @typedef {Record<string, any>} ComparableObject @@ -21,18 +20,14 @@ var isArray = Array.isArray; * * @return {boolean} Whether the two values are shallow equal. */ -function isShallowEqual( a, b ) { +export default function isShallowEqual( a, b ) { if ( a && b ) { if ( a.constructor === Object && b.constructor === Object ) { return isShallowEqualObjects( a, b ); - } else if ( isArray( a ) && isArray( b ) ) { + } else if ( Array.isArray( a ) && Array.isArray( b ) ) { return isShallowEqualArrays( a, b ); } } return a === b; } - -module.exports = isShallowEqual; -module.exports.isShallowEqualObjects = isShallowEqualObjects; -module.exports.isShallowEqualArrays = isShallowEqualArrays; diff --git a/packages/is-shallow-equal/lib/objects.js b/packages/is-shallow-equal/src/objects.js similarity index 77% rename from packages/is-shallow-equal/lib/objects.js rename to packages/is-shallow-equal/src/objects.js index 2c7583aba9ce1d..e2dc6867c7d51b 100644 --- a/packages/is-shallow-equal/lib/objects.js +++ b/packages/is-shallow-equal/src/objects.js @@ -1,7 +1,3 @@ -'use strict'; - -var keys = Object.keys; - /** * Returns true if the two objects are shallow equal, or false otherwise. * @@ -10,25 +6,23 @@ var keys = Object.keys; * * @return {boolean} Whether the two objects are shallow equal. */ -function isShallowEqualObjects( a, b ) { - var aKeys, bKeys, i, key, aValue; - +export default function isShallowEqualObjects( a, b ) { if ( a === b ) { return true; } - aKeys = keys( a ); - bKeys = keys( b ); + const aKeys = Object.keys( a ); + const bKeys = Object.keys( b ); if ( aKeys.length !== bKeys.length ) { return false; } - i = 0; + let i = 0; while ( i < aKeys.length ) { - key = aKeys[ i ]; - aValue = a[ key ]; + const key = aKeys[ i ]; + const aValue = a[ key ]; if ( // In iterating only the keys of the first object after verifying @@ -47,5 +41,3 @@ function isShallowEqualObjects( a, b ) { return true; } - -module.exports = isShallowEqualObjects; diff --git a/packages/is-shallow-equal/test/index.js b/packages/is-shallow-equal/test/index.js index 66bf538552c645..ea99282111946f 100644 --- a/packages/is-shallow-equal/test/index.js +++ b/packages/is-shallow-equal/test/index.js @@ -4,7 +4,7 @@ import isShallowEqual, { isShallowEqualArrays, isShallowEqualObjects, -} from '..'; +} from '../src'; describe( 'isShallowEqual', () => { it( 'returns false if of different types', () => { diff --git a/packages/is-shallow-equal/tsconfig.json b/packages/is-shallow-equal/tsconfig.json index 426ab13d0aa8f6..3c2c31f506f132 100644 --- a/packages/is-shallow-equal/tsconfig.json +++ b/packages/is-shallow-equal/tsconfig.json @@ -1,8 +1,8 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "lib", + "rootDir": "src", "declarationDir": "build-types" }, - "include": [ "lib/**/*" ] + "include": [ "src/**/*" ] } diff --git a/packages/jest-console/package.json b/packages/jest-console/package.json index ca2eeebc9e558b..028a5976e3a5b9 100644 --- a/packages/jest-console/package.json +++ b/packages/jest-console/package.json @@ -30,7 +30,7 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "jest-matcher-utils": "^25.3.0", "lodash": "^4.17.19" }, diff --git a/packages/jest-puppeteer-axe/CHANGELOG.md b/packages/jest-puppeteer-axe/CHANGELOG.md index 06218151ed8beb..3445543152499c 100644 --- a/packages/jest-puppeteer-axe/CHANGELOG.md +++ b/packages/jest-puppeteer-axe/CHANGELOG.md @@ -2,16 +2,16 @@ ## Unreleased -## New Features +### Breaking Changes -- The `axe-puppeteer` dependency has been updated from requiring `^1.0.0` to requiring `^1.1.0`. +- Migrated `axe-puppeteer` to its new package [@axe-core/puppeteer](https://github.com/dequelabs/axe-core-npm/tree/develop/packages/puppeteer) that contains breaking changes. ## 1.1.0 (2019-05-21) ### New Feature -- Added optional `disabledRules` option to use with `toPassAxeTests` matcher. +- Added optional `disabledRules` option to use with `toPassAxeTests` matcher. ## 1.0.0 (2019-03-06) -- Initial release. +- Initial release. diff --git a/packages/jest-puppeteer-axe/package.json b/packages/jest-puppeteer-axe/package.json index 8efcfce0e6b09c..4174ca44a0abf8 100644 --- a/packages/jest-puppeteer-axe/package.json +++ b/packages/jest-puppeteer-axe/package.json @@ -31,8 +31,8 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.11.2", - "axe-puppeteer": "^1.1.0" + "@axe-core/puppeteer": "^4.0.0", + "@babel/runtime": "^7.12.5" }, "peerDependencies": { "jest": ">=24", diff --git a/packages/jest-puppeteer-axe/src/index.js b/packages/jest-puppeteer-axe/src/index.js index 4846c198dd0cf7..587de4f8a142f2 100644 --- a/packages/jest-puppeteer-axe/src/index.js +++ b/packages/jest-puppeteer-axe/src/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import AxePuppeteer from 'axe-puppeteer'; +import AxePuppeteer from '@axe-core/puppeteer'; /** @typedef {import('puppeteer').Page} Page */ @@ -60,7 +60,7 @@ function formatViolations( violations ) { * @see https://www.deque.com/axe/ * It is possible to pass optional Axe API options to perform customized check. * - * @see https://github.com/dequelabs/axe-puppeteer + * @see https://github.com/dequelabs/axe-core-npm/tree/develop/packages/puppeteer * * @param {Page} page Puppeteer's page instance. * @param {?Object} params Optional params that allow better control over Axe API. diff --git a/packages/keyboard-shortcuts/CHANGELOG.md b/packages/keyboard-shortcuts/CHANGELOG.md index 12970d3948183a..25ddc903fc4593 100644 --- a/packages/keyboard-shortcuts/CHANGELOG.md +++ b/packages/keyboard-shortcuts/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### New Feature + +- Added a store definition `store` for the keyboard shortcuts namespace to use with `@wordpress/data` API ([#26655](https://github.com/WordPress/gutenberg/pull/26655)). + ## 1.0.0 (2020-02-04) Initial release. diff --git a/packages/keyboard-shortcuts/README.md b/packages/keyboard-shortcuts/README.md index c9726284d461e3..27d7432447bd12 100644 --- a/packages/keyboard-shortcuts/README.md +++ b/packages/keyboard-shortcuts/README.md @@ -16,6 +16,18 @@ _This package assumes that your code will run in an **ES2015+** environment. If <!-- START TOKEN(Autogenerated API docs) --> +<a name="store" href="#store">#</a> **store** + +Store definition for the keyboard shortcuts namespace. + +_Related_ + +- <https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore> + +_Type_ + +- `Object` + <a name="useShortcut" href="#useShortcut">#</a> **useShortcut** Attach a keyboard shortcut handler. diff --git a/packages/keyboard-shortcuts/package.json b/packages/keyboard-shortcuts/package.json index 644ab39c616701..62eaf4fb98b773 100644 --- a/packages/keyboard-shortcuts/package.json +++ b/packages/keyboard-shortcuts/package.json @@ -22,7 +22,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", "@wordpress/element": "file:../element", diff --git a/packages/keyboard-shortcuts/src/hooks/use-shortcut.js b/packages/keyboard-shortcuts/src/hooks/use-shortcut.js index f6b0f4e584b90f..7447209d5ba713 100644 --- a/packages/keyboard-shortcuts/src/hooks/use-shortcut.js +++ b/packages/keyboard-shortcuts/src/hooks/use-shortcut.js @@ -4,6 +4,11 @@ import { useSelect } from '@wordpress/data'; import { useKeyboardShortcut } from '@wordpress/compose'; +/** + * Internal dependencies + */ +import { store as keyboardShortcutsStore } from '../store'; + /** * Attach a keyboard shortcut handler. * @@ -15,7 +20,7 @@ function useShortcut( name, callback, options ) { const shortcuts = useSelect( ( select ) => { return select( - 'core/keyboard-shortcuts' + keyboardShortcutsStore ).getAllShortcutRawKeyCombinations( name ); }, [ name ] diff --git a/packages/keyboard-shortcuts/src/index.js b/packages/keyboard-shortcuts/src/index.js index 9a738b710b2dab..a57718af9c566e 100644 --- a/packages/keyboard-shortcuts/src/index.js +++ b/packages/keyboard-shortcuts/src/index.js @@ -1,6 +1,2 @@ -/** - * Internal dependencies - */ -import './store'; - +export { store } from './store'; export { default as useShortcut } from './hooks/use-shortcut'; diff --git a/packages/keyboard-shortcuts/src/store/index.js b/packages/keyboard-shortcuts/src/store/index.js index aadabb955d7526..0f40e3f4d74829 100644 --- a/packages/keyboard-shortcuts/src/store/index.js +++ b/packages/keyboard-shortcuts/src/store/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { registerStore } from '@wordpress/data'; +import { createReduxStore, register } from '@wordpress/data'; /** * Internal dependencies @@ -10,8 +10,19 @@ import reducer from './reducer'; import * as actions from './actions'; import * as selectors from './selectors'; -export default registerStore( 'core/keyboard-shortcuts', { +const STORE_NAME = 'core/keyboard-shortcuts'; + +/** + * Store definition for the keyboard shortcuts namespace. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore + * + * @type {Object} + */ +export const store = createReduxStore( STORE_NAME, { reducer, actions, selectors, } ); + +register( store ); diff --git a/packages/keycodes/package.json b/packages/keycodes/package.json index 536e77bd6d92b4..67ff56951b87e7 100644 --- a/packages/keycodes/package.json +++ b/packages/keycodes/package.json @@ -23,7 +23,7 @@ "react-native": "src/index", "sideEffects": false, "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/i18n": "file:../i18n", "lodash": "^4.17.19" }, diff --git a/packages/lazy-import/CHANGELOG.md b/packages/lazy-import/CHANGELOG.md index 69e59470765d81..551a536b84645d 100644 --- a/packages/lazy-import/CHANGELOG.md +++ b/packages/lazy-import/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### New Feature + +- Allow local paths as an option when trying to import a specific file from the package ([#23751](https://github.com/WordPress/gutenberg/pull/23751)). + ## 1.0.0 (2020-06-15) -- Initial release. +- Initial release. diff --git a/packages/lazy-import/README.md b/packages/lazy-import/README.md index 03a6e14b78cc5f..06a943f23ce7d7 100644 --- a/packages/lazy-import/README.md +++ b/packages/lazy-import/README.md @@ -52,7 +52,10 @@ function onInstall() { console.log( 'Installing…' ); } -lazyImport( 'is-equal-shallow@^0.1.3', { onInstall } ).then( /* ... */ ); +lazyImport( 'fbjs@^1.0.0', { + localPath: './lib/shallowEqual', + onInstall, +} ).then(/* ... */); ``` Note that `lazyImport` can throw an error when offline and unable to install the dependency using NPM. You may want to anticipate this and provide remediation steps for a failed install, such as logging a warning messsage: @@ -69,10 +72,17 @@ try { ### Options +#### `localPath` + +- Type: `string` +- Required: No + +Local path pointing to a file or directory that can be used when other script that `main` needs to be imported. + #### `onInstall` -- Type: `Function` -- Required: No +- Type: `Function` +- Required: No Function to call if and when the module is being installed. Since installation can cause a delay in script execution, this can be useful to output logging information or display a spinner. diff --git a/packages/lazy-import/lib/index.js b/packages/lazy-import/lib/index.js index bb68fd7936bce6..46ecbb53b0229d 100644 --- a/packages/lazy-import/lib/index.js +++ b/packages/lazy-import/lib/index.js @@ -10,7 +10,8 @@ const { createHash } = require( 'crypto' ); /** * @typedef WPLazyImportOptions * - * @property {()=>void} onInstall Callback to invoke when install starts. + * @property {string} [localPath] Path to the local directory or file. + * @property {()=>void} [onInstall] Callback to invoke when install starts. */ /** @@ -63,6 +64,7 @@ async function install( arg, alias ) { * @return {Promise<NodeRequire>} Promise resolving to required module. */ async function lazyImport( arg, options = {} ) { + const { localPath = '' } = options; const { rawSpec, name } = npmPackageArg( arg ); if ( ! name ) { @@ -77,7 +79,9 @@ async function lazyImport( arg, options = {} ) { // need to verify both availability and version. Version isn't necessary to // account for in this first attempt. try { - return require( localModule ); + if ( require.resolve( localModule ) ) { + return require( join( localModule, localPath ) ); + } } catch ( error ) { if ( error.code !== 'MODULE_NOT_FOUND' ) { throw error; @@ -85,13 +89,11 @@ async function lazyImport( arg, options = {} ) { } try { - const resolved = require( name ); - const { version } = require( join( name, 'package.json' ) ); if ( semver.satisfies( version, rawSpec ) ) { // Only return with the resolved module if the version is valid per // the parsed arg. Otherwise, fall through to install stage. - return resolved; + return require( join( name, localPath ) ); } } catch ( error ) { if ( error.code !== 'MODULE_NOT_FOUND' ) { @@ -133,7 +135,7 @@ async function lazyImport( arg, options = {} ) { // // See: https://github.com/WordPress/gutenberg/pull/22684#discussion_r434583858 - return require( localModule ); + return require( join( localModule, localPath ) ); } module.exports = lazyImport; diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index 7e61f085427c85..02d94aa699808e 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/list-reusable-blocks", - "version": "1.24.1", + "version": "1.24.2", "description": "Adding Export/Import support to the reusable blocks listing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -22,7 +22,7 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", diff --git a/packages/media-utils/package.json b/packages/media-utils/package.json index 9cab8621a76926..c537ac80f33891 100644 --- a/packages/media-utils/package.json +++ b/packages/media-utils/package.json @@ -22,7 +22,7 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/blob": "file:../blob", "@wordpress/element": "file:../element", diff --git a/packages/notices/CHANGELOG.md b/packages/notices/CHANGELOG.md index dbe2fad7a0e713..1f6ed1d38a6736 100644 --- a/packages/notices/CHANGELOG.md +++ b/packages/notices/CHANGELOG.md @@ -2,17 +2,21 @@ ## Unreleased +### New Feature + +- Added a store definition `store` for the notices namespace to use with `@wordpress/data` API ([#26655](https://github.com/WordPress/gutenberg/pull/26655)). + ## 2.0.0 (2020-02-10) ### Breaking Change -- A notices message is no longer spoken as a result of notice creation, but rather by its display in the interface by its corresponding [`Notice` component](https://github.com/WordPress/gutenberg/tree/master/packages/components/src/notice). +- A notices message is no longer spoken as a result of notice creation, but rather by its display in the interface by its corresponding [`Notice` component](https://github.com/WordPress/gutenberg/tree/master/packages/components/src/notice). ## 1.5.0 (2019-06-12) ### New Features -- Support a new `snackbar` notice type in the `createNotice` action. +- Support a new `snackbar` notice type in the `createNotice` action. ## 1.1.2 (2019-01-03) @@ -22,11 +26,11 @@ ### New Feature -- New option `speak` enables control as to whether the notice content is announced to screen readers (defaults to `true`) +- New option `speak` enables control as to whether the notice content is announced to screen readers (defaults to `true`) ### Bug Fixes -- While `createNotice` only explicitly supported content of type `string`, it was not previously enforced. This has been corrected. +- While `createNotice` only explicitly supported content of type `string`, it was not previously enforced. This has been corrected. ## 1.0.5 (2018-11-15) @@ -40,4 +44,4 @@ ## 1.0.0 (2018-10-29) -- Initial release. +- Initial release. diff --git a/packages/notices/package.json b/packages/notices/package.json index 1fc7c40f86fc4e..89988271af7a87 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -22,7 +22,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/a11y": "file:../a11y", "@wordpress/data": "file:../data", "lodash": "^4.17.19" diff --git a/packages/notices/src/index.js b/packages/notices/src/index.js index a7ff64df3ef2a3..33b78d5a1d117e 100644 --- a/packages/notices/src/index.js +++ b/packages/notices/src/index.js @@ -1,4 +1 @@ -/** - * Internal dependencies - */ -import './store'; +export { store } from './store'; diff --git a/packages/notices/src/store/actions.js b/packages/notices/src/store/actions.js index 9f9acc3665e277..ad25f01379fe25 100644 --- a/packages/notices/src/store/actions.js +++ b/packages/notices/src/store/actions.js @@ -40,6 +40,12 @@ import { DEFAULT_CONTEXT, DEFAULT_STATUS } from './constants'; * readers. * @param {Array<WPNoticeAction>} [options.actions] User actions to be * presented with notice. + * @param {Object} [options.icon] An icon displayed with the notice. + * @param {boolean} [options.explicitDismiss] Whether the notice includes + * an explict dismiss button and + * can't be dismissed by clicking + * the body of the notice. + * @param {Function} [options.onDismiss] Called when the notice is dismissed. * * @return {Object} Action object. */ @@ -52,6 +58,9 @@ export function createNotice( status = DEFAULT_STATUS, content, options = {} ) { actions = [], type = 'default', __unstableHTML, + icon = null, + explicitDismiss = false, + onDismiss = null, } = options; // The supported value shape of content is currently limited to plain text @@ -71,6 +80,9 @@ export function createNotice( status = DEFAULT_STATUS, content, options = {} ) { isDismissible, actions, type, + icon, + explicitDismiss, + onDismiss, }, }; } diff --git a/packages/notices/src/store/index.js b/packages/notices/src/store/index.js index 22a70e02ebb903..12b5b3a66e7965 100644 --- a/packages/notices/src/store/index.js +++ b/packages/notices/src/store/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { registerStore } from '@wordpress/data'; +import { createReduxStore, register } from '@wordpress/data'; /** * Internal dependencies @@ -10,8 +10,17 @@ import reducer from './reducer'; import * as actions from './actions'; import * as selectors from './selectors'; -export default registerStore( 'core/notices', { +/** + * Store definition for the notices namespace. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore + * + * @type {Object} + */ +export const store = createReduxStore( 'core/notices', { reducer, actions, selectors, } ); + +register( store ); diff --git a/packages/notices/src/store/test/actions.js b/packages/notices/src/store/test/actions.js index 44923e66a36274..1deb16807b4340 100644 --- a/packages/notices/src/store/test/actions.js +++ b/packages/notices/src/store/test/actions.js @@ -57,6 +57,7 @@ describe( 'actions', () => { id, isDismissible: false, context: 'foo', + icon: '🌮', }; const result = createNotice( status, content, options ); @@ -69,9 +70,13 @@ describe( 'actions', () => { status, content, spokenMessage: content, + __unstableHTML: undefined, isDismissible: false, actions: [], type: 'default', + icon: '🌮', + explicitDismiss: false, + onDismiss: null, }, } ); } ); @@ -100,6 +105,9 @@ describe( 'actions', () => { isDismissible: false, actions: [], type: 'default', + icon: null, + explicitDismiss: false, + onDismiss: null, }, } ); } ); diff --git a/packages/notices/src/store/test/reducer.js b/packages/notices/src/store/test/reducer.js index 4ae106de4314fa..2016d7c12d8397 100644 --- a/packages/notices/src/store/test/reducer.js +++ b/packages/notices/src/store/test/reducer.js @@ -28,10 +28,14 @@ describe( 'reducer', () => { id: expect.any( String ), content: 'save error', spokenMessage: 'save error', + __unstableHTML: undefined, status: 'error', isDismissible: true, actions: [], type: 'default', + icon: null, + explicitDismiss: false, + onDismiss: null, }, ], } ); @@ -49,10 +53,14 @@ describe( 'reducer', () => { id: expect.any( String ), content: 'save error', spokenMessage: 'save error', + __unstableHTML: undefined, status: 'error', isDismissible: true, actions: [], type: 'default', + icon: null, + explicitDismiss: false, + onDismiss: null, }, ], } ); @@ -71,19 +79,27 @@ describe( 'reducer', () => { id: expect.any( String ), content: 'save error', spokenMessage: 'save error', + __unstableHTML: undefined, status: 'error', isDismissible: true, actions: [], type: 'default', + icon: null, + explicitDismiss: false, + onDismiss: null, }, { id: expect.any( String ), content: 'successfully saved', spokenMessage: 'successfully saved', + __unstableHTML: undefined, status: 'success', isDismissible: true, actions: [], type: 'default', + icon: null, + explicitDismiss: false, + onDismiss: null, }, ], } ); @@ -142,10 +158,14 @@ describe( 'reducer', () => { id: 'error-message', content: 'save error (2)', spokenMessage: 'save error (2)', + __unstableHTML: undefined, status: 'error', isDismissible: true, actions: [], type: 'default', + icon: null, + explicitDismiss: false, + onDismiss: null, }, ], } ); diff --git a/packages/nux/CHANGELOG.md b/packages/nux/CHANGELOG.md index 7687dd92c91c3e..61be951fcd8d4a 100644 --- a/packages/nux/CHANGELOG.md +++ b/packages/nux/CHANGELOG.md @@ -2,9 +2,13 @@ ## Unreleased +### New Feature + +- Added a store definition `store` for the core data namespace to use with `@wordpress/data` API ([#26655](https://github.com/WordPress/gutenberg/pull/26655)). + # 3.1.0 (2019-06-03) -- The `@wordpress/nux` package has been deprecated. Please use the `Guide` component in `@wordpress/components` to show a user guide. +- The `@wordpress/nux` package has been deprecated. Please use the `Guide` component in `@wordpress/components` to show a user guide. ## 3.0.6 (2019-01-03) @@ -22,7 +26,7 @@ ### Breaking Changes -- The id prop of DotTip has been removed. Please use the tipId prop instead. +- The id prop of DotTip has been removed. Please use the tipId prop instead. ## 2.0.13 (2018-11-12) @@ -40,7 +44,7 @@ ### Deprecations -- The id prop of DotTip has been deprecated. Please use the tipId prop instead. +- The id prop of DotTip has been deprecated. Please use the tipId prop instead. ## 2.0.6 (2018-10-22) @@ -52,4 +56,4 @@ ### Breaking Change -- Change how required built-ins are polyfilled with Babel 7 ([#9171](https://github.com/WordPress/gutenberg/pull/9171)). If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. +- Change how required built-ins are polyfilled with Babel 7 ([#9171](https://github.com/WordPress/gutenberg/pull/9171)). If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. diff --git a/packages/nux/package.json b/packages/nux/package.json index 4eea1042328a5b..c4e2d9eba16572 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/nux", - "version": "3.23.1", + "version": "3.23.2", "description": "NUX (New User eXperience) module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -23,10 +23,11 @@ "react-native": "src/index", "sideEffects": [ "build-style/**", - "!((src|build|build-module)/components/**)" + "src/**/*.scss", + "{src,build,build-module}/{index.js,store/index.js}" ], "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", diff --git a/packages/nux/src/index.js b/packages/nux/src/index.js index ffead08b9e4c2d..ffd4a5e8818828 100644 --- a/packages/nux/src/index.js +++ b/packages/nux/src/index.js @@ -3,11 +3,7 @@ */ import deprecated from '@wordpress/deprecated'; -/** - * Internal dependencies - */ -import './store'; - +export { store } from './store'; export { default as DotTip } from './components/dot-tip'; deprecated( 'wp.nux', { diff --git a/packages/nux/src/store/index.js b/packages/nux/src/store/index.js index 8f3e8af338dab2..ccd79e59ec1e16 100644 --- a/packages/nux/src/store/index.js +++ b/packages/nux/src/store/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { registerStore } from '@wordpress/data'; +import { createReduxStore, registerStore } from '@wordpress/data'; /** * Internal dependencies @@ -10,11 +10,27 @@ import reducer from './reducer'; import * as actions from './actions'; import * as selectors from './selectors'; -const store = registerStore( 'core/nux', { +const STORE_NAME = 'core/nux'; + +/** + * Store definition for the nux namespace. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore + * + * @type {Object} + */ +export const store = createReduxStore( STORE_NAME, { reducer, actions, selectors, persist: [ 'preferences' ], } ); -export default store; +// Once we build a more generic persistence plugin that works across types of stores +// we'd be able to replace this with a register call. +registerStore( STORE_NAME, { + reducer, + actions, + selectors, + persist: [ 'preferences' ], +} ); diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 37c3ee207f4ce3..ea90d26a00d5da 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -22,7 +22,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/compose": "file:../compose", "@wordpress/element": "file:../element", "@wordpress/hooks": "file:../hooks", diff --git a/packages/primitives/package.json b/packages/primitives/package.json index 3ea6ec5087b8ee..0014876e3ae05a 100644 --- a/packages/primitives/package.json +++ b/packages/primitives/package.json @@ -22,10 +22,12 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", - "sideEffects": false, + "sideEffects": [ + "src/**/*.scss" + ], "types": "build-types", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/element": "file:../element", "classnames": "^2.2.5" }, diff --git a/packages/primitives/src/block-quotation/index.native.js b/packages/primitives/src/block-quotation/index.native.js index 076314e3782153..8f97ed8dc43d81 100644 --- a/packages/primitives/src/block-quotation/index.native.js +++ b/packages/primitives/src/block-quotation/index.native.js @@ -13,20 +13,33 @@ import { withPreferredColorScheme } from '@wordpress/compose'; import styles from './style.scss'; export const BlockQuotation = withPreferredColorScheme( ( props ) => { - const { getStylesFromColorScheme } = props; + const { getStylesFromColorScheme, style } = props; + + const blockQuoteStyle = [ + getStylesFromColorScheme( + styles.wpBlockQuoteLight, + styles.wpBlockQuoteDark + ), + style?.color && { + borderLeftColor: style.color, + }, + ]; + const colorStyle = style?.color ? { color: style.color } : {}; - const blockQuoteStyle = getStylesFromColorScheme( - styles.wpBlockQuoteLight, - styles.wpBlockQuoteDark - ); const newChildren = Children.map( props.children, ( child ) => { if ( child && child.props.identifier === 'citation' ) { return cloneElement( child, { - style: styles.wpBlockQuoteCitation, + style: { + ...styles.wpBlockQuoteCitation, + ...colorStyle, + }, } ); } if ( child && child.props.identifier === 'value' ) { - return cloneElement( child, { tagsToEliminate: [ 'div' ] } ); + return cloneElement( child, { + tagsToEliminate: [ 'div' ], + style: colorStyle, + } ); } return child; } ); diff --git a/packages/primitives/src/horizontal-rule/index.native.js b/packages/primitives/src/horizontal-rule/index.native.js index 315fc3d94f38d7..853b57e76d0c1a 100644 --- a/packages/primitives/src/horizontal-rule/index.native.js +++ b/packages/primitives/src/horizontal-rule/index.native.js @@ -3,12 +3,27 @@ */ import Hr from 'react-native-hr'; -export const HorizontalRule = ( props ) => { - const lineStyle = { - backgroundColor: '#555d66', - height: 2, - ...props.lineStyle, - }; - - return <Hr { ...props } lineStyle={ lineStyle } />; +/** + * WordPress dependencies + */ +import { withPreferredColorScheme } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import styles from './styles.scss'; + +const HR = ( { getStylesFromColorScheme, ...props } ) => { + const lineStyle = getStylesFromColorScheme( styles.line, styles.lineDark ); + + return ( + <Hr + { ...props } + lineStyle={ [ lineStyle, props.lineStyle ] } + marginLeft={ 0 } + marginRight={ 0 } + /> + ); }; + +export const HorizontalRule = withPreferredColorScheme( HR ); diff --git a/packages/primitives/src/horizontal-rule/styles.native.scss b/packages/primitives/src/horizontal-rule/styles.native.scss new file mode 100644 index 00000000000000..dabb62a8336542 --- /dev/null +++ b/packages/primitives/src/horizontal-rule/styles.native.scss @@ -0,0 +1,8 @@ +.line { + background-color: $gray-lighten-20; + height: 2; +} + +.lineDark { + background-color: $gray-50; +} diff --git a/packages/priority-queue/package.json b/packages/priority-queue/package.json index 5157a4baa15517..7ac9934a039397 100644 --- a/packages/priority-queue/package.json +++ b/packages/priority-queue/package.json @@ -25,7 +25,7 @@ "types": "build-types", "sideEffects": false, "dependencies": { - "@babel/runtime": "^7.11.2" + "@babel/runtime": "^7.12.5" }, "publishConfig": { "access": "public" diff --git a/packages/project-management-automation/lib/if-not-fork.js b/packages/project-management-automation/lib/if-not-fork.js deleted file mode 100644 index 7ab214026cf8bd..00000000000000 --- a/packages/project-management-automation/lib/if-not-fork.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Internal dependencies - */ -const debug = require( './debug' ); - -/** @typedef {import('.').WPAutomationTask} WPAutomationTask */ - -/** - * Higher-order function which executes and returns the result of the given - * handler only if the enhanced function is called with a payload indicating a - * pull request event which did not originate from a forked repository. - * - * @param {WPAutomationTask} handler Original task. - * - * @return {WPAutomationTask} Enhanced task. - */ -function ifNotFork( handler ) { - /** @type {WPAutomationTask} */ - const newHandler = ( payload, octokit ) => { - if ( - payload.pull_request.head.repo.full_name === - payload.pull_request.base.repo.full_name - ) { - return handler( payload, octokit ); - } - debug( `main: Skipping ${ handler.name } because we are in a fork.` ); - }; - Object.defineProperty( newHandler, 'name', { value: handler.name } ); - return newHandler; -} - -module.exports = ifNotFork; diff --git a/packages/project-management-automation/lib/index.js b/packages/project-management-automation/lib/index.js index 37d83953b5af9c..a27a4431a945ef 100644 --- a/packages/project-management-automation/lib/index.js +++ b/packages/project-management-automation/lib/index.js @@ -8,10 +8,10 @@ const { context, GitHub } = require( '@actions/github' ); * Internal dependencies */ const assignFixedIssues = require( './tasks/assign-fixed-issues' ); -const firstTimeContributor = require( './tasks/first-time-contributor' ); +const firstTimeContributorAccountLink = require( './tasks/first-time-contributor-account-link' ); +const firstTimeContributorLabel = require( './tasks/first-time-contributor-label' ); const addMilestone = require( './tasks/add-milestone' ); const debug = require( './debug' ); -const ifNotFork = require( './if-not-fork' ); /** @typedef {import('@actions/github').GitHub} GitHub */ @@ -37,13 +37,18 @@ const ifNotFork = require( './if-not-fork' ); */ const automations = [ { - event: 'pull_request', + event: 'pull_request_target', action: 'opened', - task: ifNotFork( assignFixedIssues ), + task: assignFixedIssues, + }, + { + event: 'pull_request_target', + action: 'opened', + task: firstTimeContributorLabel, }, { event: 'push', - task: firstTimeContributor, + task: firstTimeContributorAccountLink, }, { event: 'push', diff --git a/packages/project-management-automation/lib/tasks/first-time-contributor/README.md b/packages/project-management-automation/lib/tasks/first-time-contributor-account-link/README.md similarity index 68% rename from packages/project-management-automation/lib/tasks/first-time-contributor/README.md rename to packages/project-management-automation/lib/tasks/first-time-contributor-account-link/README.md index 700fea1d201bca..45c6591c83a016 100644 --- a/packages/project-management-automation/lib/tasks/first-time-contributor/README.md +++ b/packages/project-management-automation/lib/tasks/first-time-contributor-account-link/README.md @@ -1,7 +1,7 @@ -First Time Contributor +First Time Contributor Account Link === -Adds the "First Time Contributor" label to pull requests merged on behalf of contributors that have not previously made a contribution, and prompts the user to link their GitHub account to their WordPress.org profile if necessary for release notes credit. +Prompts the user to link their GitHub account to their WordPress.org profile if necessary for release notes credit. ## Rationale diff --git a/packages/project-management-automation/lib/tasks/first-time-contributor/index.js b/packages/project-management-automation/lib/tasks/first-time-contributor-account-link/index.js similarity index 66% rename from packages/project-management-automation/lib/tasks/first-time-contributor/index.js rename to packages/project-management-automation/lib/tasks/first-time-contributor-account-link/index.js index 80c200cc54de54..1f5609750f0bf8 100644 --- a/packages/project-management-automation/lib/tasks/first-time-contributor/index.js +++ b/packages/project-management-automation/lib/tasks/first-time-contributor-account-link/index.js @@ -33,17 +33,17 @@ function getPromptMessageText( author ) { } /** - * Adds the 'First Time Contributor' label to PRs merged on behalf of - * contributors that have not yet made a commit, and prompts the user to link - * their GitHub account to their WordPress.org profile if neccessary for props - * credit. + * Prompts the user to link their GitHub account to their WordPress.org profile + * if neccessary for props credit. * * @param {WebhookPayloadPush} payload Push event payload. * @param {GitHub} octokit Initialized Octokit REST client. */ -async function firstTimeContributor( payload, octokit ) { +async function firstTimeContributorAccountLink( payload, octokit ) { if ( payload.ref !== 'refs/heads/master' ) { - debug( 'first-time-contributor: Commit is not to `master`. Aborting' ); + debug( + 'first-time-contributor-account-link: Commit is not to `master`. Aborting' + ); return; } @@ -52,7 +52,7 @@ async function firstTimeContributor( payload, octokit ) { const pullRequest = getAssociatedPullRequest( commit ); if ( ! pullRequest ) { debug( - 'first-time-contributor: Cannot determine pull request associated with commit. Aborting' + 'first-time-contributor-account-link: Cannot determine pull request associated with commit. Aborting' ); return; } @@ -60,8 +60,9 @@ async function firstTimeContributor( payload, octokit ) { const repo = payload.repository.name; const owner = payload.repository.owner.login; const author = commit.author.username; + debug( - `first-time-contributor: Searching for commits in ${ owner }/${ repo } by @${ author }` + `first-time-contributor-account-link: Searching for commits in ${ owner }/${ repo } by @${ author }` ); const { data: commits } = await octokit.repos.listCommits( { @@ -72,24 +73,13 @@ async function firstTimeContributor( payload, octokit ) { if ( commits.length > 1 ) { debug( - `first-time-contributor: Not the first commit for author. Aborting` + `first-time-contributor-account-link: Not the first commit for author. Aborting` ); return; } debug( - `first-time-contributor: Adding 'First Time Contributor' label to issue #${ pullRequest }` - ); - - await octokit.issues.addLabels( { - owner, - repo, - issue_number: pullRequest, - labels: [ 'First-time Contributor' ], - } ); - - debug( - `first-time-contributor: Checking for WordPress username associated with @${ author }` + `first-time-contributor-account-link: Checking for WordPress username associated with @${ author }` ); let hasProfile; @@ -97,20 +87,20 @@ async function firstTimeContributor( payload, octokit ) { hasProfile = await hasWordPressProfile( author ); } catch ( error ) { debug( - `first-time-contributor: Error retrieving from profile API:\n\n${ error.toString() }` + `first-time-contributor-account-link: Error retrieving from profile API:\n\n${ error.toString() }` ); return; } if ( hasProfile ) { debug( - `first-time-contributor: User already known. No need to prompt for account link!` + `first-time-contributor-account-link: User already known. No need to prompt for account link!` ); return; } debug( - 'first-time-contributor: User not known. Adding comment to prompt for account link.' + 'first-time-contributor-account-link: User not known. Adding comment to prompt for account link.' ); await octokit.issues.createComment( { @@ -121,4 +111,4 @@ async function firstTimeContributor( payload, octokit ) { } ); } -module.exports = firstTimeContributor; +module.exports = firstTimeContributorAccountLink; diff --git a/packages/project-management-automation/lib/tasks/first-time-contributor/test/index.js b/packages/project-management-automation/lib/tasks/first-time-contributor-account-link/test/index.js similarity index 67% rename from packages/project-management-automation/lib/tasks/first-time-contributor/test/index.js rename to packages/project-management-automation/lib/tasks/first-time-contributor-account-link/test/index.js index e419f961be217b..523ef0603b6e01 100644 --- a/packages/project-management-automation/lib/tasks/first-time-contributor/test/index.js +++ b/packages/project-management-automation/lib/tasks/first-time-contributor-account-link/test/index.js @@ -1,12 +1,12 @@ /** * Internal dependencies */ -import firstTimeContributor from '../'; +import firstTimeContributorAccountLink from '../'; import hasWordPressProfile from '../../../has-wordpress-profile'; jest.mock( '../../../has-wordpress-profile', () => jest.fn() ); -describe( 'firstTimeContributor', () => { +describe( 'firstTimeContributorAccountLink', () => { beforeEach( () => { hasWordPressProfile.mockReset(); } ); @@ -44,7 +44,7 @@ describe( 'firstTimeContributor', () => { }, }; - await firstTimeContributor( payloadForBranchPush, octokit ); + await firstTimeContributorAccountLink( payloadForBranchPush, octokit ); expect( octokit.repos.listCommits ).not.toHaveBeenCalled(); } ); @@ -70,7 +70,7 @@ describe( 'firstTimeContributor', () => { }, }; - await firstTimeContributor( payloadDirectToMaster, octokit ); + await firstTimeContributorAccountLink( payloadDirectToMaster, octokit ); expect( octokit.repos.listCommits ).not.toHaveBeenCalled(); } ); @@ -88,52 +88,17 @@ describe( 'firstTimeContributor', () => { ), }, issues: { - addLabels: jest.fn(), - }, - }; - - await firstTimeContributor( payload, octokit ); - - expect( octokit.repos.listCommits ).toHaveBeenCalledWith( { - owner: 'WordPress', - repo: 'gutenberg', - author: 'ghost', - } ); - expect( octokit.issues.addLabels ).not.toHaveBeenCalled(); - } ); - - it( 'adds the label if this was the first commit for the user', async () => { - const octokit = { - repos: { - listCommits: jest.fn( () => - Promise.resolve( { - data: [ - { sha: '4c535288a6a2b75ff23ee96c75f7d9877e919241' }, - ], - } ) - ), - }, - issues: { - addLabels: jest.fn(), createComment: jest.fn(), }, }; - hasWordPressProfile.mockReturnValue( Promise.resolve( true ) ); - - await firstTimeContributor( payload, octokit ); + await firstTimeContributorAccountLink( payload, octokit ); expect( octokit.repos.listCommits ).toHaveBeenCalledWith( { owner: 'WordPress', repo: 'gutenberg', author: 'ghost', } ); - expect( octokit.issues.addLabels ).toHaveBeenCalledWith( { - owner: 'WordPress', - repo: 'gutenberg', - issue_number: 123, - labels: [ 'First-time Contributor' ], - } ); expect( octokit.issues.createComment ).not.toHaveBeenCalled(); } ); @@ -149,7 +114,6 @@ describe( 'firstTimeContributor', () => { ), }, issues: { - addLabels: jest.fn(), createComment: jest.fn(), }, }; @@ -158,19 +122,13 @@ describe( 'firstTimeContributor', () => { return Promise.reject( new Error( 'Whoops!' ) ); } ); - await firstTimeContributor( payload, octokit ); + await firstTimeContributorAccountLink( payload, octokit ); expect( octokit.repos.listCommits ).toHaveBeenCalledWith( { owner: 'WordPress', repo: 'gutenberg', author: 'ghost', } ); - expect( octokit.issues.addLabels ).toHaveBeenCalledWith( { - owner: 'WordPress', - repo: 'gutenberg', - issue_number: 123, - labels: [ 'First-time Contributor' ], - } ); expect( octokit.issues.createComment ).not.toHaveBeenCalled(); } ); @@ -186,26 +144,19 @@ describe( 'firstTimeContributor', () => { ), }, issues: { - addLabels: jest.fn(), createComment: jest.fn(), }, }; hasWordPressProfile.mockReturnValue( Promise.resolve( false ) ); - await firstTimeContributor( payload, octokit ); + await firstTimeContributorAccountLink( payload, octokit ); expect( octokit.repos.listCommits ).toHaveBeenCalledWith( { owner: 'WordPress', repo: 'gutenberg', author: 'ghost', } ); - expect( octokit.issues.addLabels ).toHaveBeenCalledWith( { - owner: 'WordPress', - repo: 'gutenberg', - issue_number: 123, - labels: [ 'First-time Contributor' ], - } ); expect( octokit.issues.createComment ).toHaveBeenCalledWith( { owner: 'WordPress', repo: 'gutenberg', diff --git a/packages/project-management-automation/lib/tasks/first-time-contributor-label/README.md b/packages/project-management-automation/lib/tasks/first-time-contributor-label/README.md new file mode 100644 index 00000000000000..b73fab09ecfdd0 --- /dev/null +++ b/packages/project-management-automation/lib/tasks/first-time-contributor-label/README.md @@ -0,0 +1,8 @@ +First Time Contributor Label +=== + +Adds the "First Time Contributor" label to pull requests merged on behalf of contributors that have not previously made a contribution. + +## Rationale + +The label is useful for pull request reviewers in identifying newcomers to the project who can be offered extra guidance. diff --git a/packages/project-management-automation/lib/tasks/first-time-contributor-label/index.js b/packages/project-management-automation/lib/tasks/first-time-contributor-label/index.js new file mode 100644 index 00000000000000..fdc16161db62c7 --- /dev/null +++ b/packages/project-management-automation/lib/tasks/first-time-contributor-label/index.js @@ -0,0 +1,51 @@ +/** + * Internal dependencies + */ +const debug = require( '../../debug' ); + +/** @typedef {import('@actions/github').GitHub} GitHub */ +/** @typedef {import('@octokit/webhooks').WebhookPayloadPullRequest} WebhookPayloadPullRequest */ + +/** + * Assigns the first-time contributor label to PRs. + * + * @param {WebhookPayloadPullRequest} payload Pull request event payload. + * @param {GitHub} octokit Initialized Octokit REST client. + */ +async function firstTimeContributorLabel( payload, octokit ) { + const repo = payload.repository.name; + const owner = payload.repository.owner.login; + const author = payload.pull_request.user.login; + + debug( + `first-time-contributor: Searching for commits in ${ owner }/${ repo } by @${ author }` + ); + + const { data: commits } = await octokit.repos.listCommits( { + owner, + repo, + author, + } ); + + if ( commits.length > 0 ) { + debug( + `first-time-contributor-label: Not the first commit for author. Aborting` + ); + return; + } + + const pullRequestNumber = payload.pull_request.number; + + debug( + `first-time-contributor-label: Adding 'First Time Contributor' label to pr #${ pullRequestNumber }` + ); + + await octokit.issues.addLabels( { + owner, + repo, + issue_number: payload.pull_request.number, + labels: [ 'First-time Contributor' ], + } ); +} + +module.exports = firstTimeContributorLabel; diff --git a/packages/project-management-automation/lib/tasks/first-time-contributor-label/test/index.js b/packages/project-management-automation/lib/tasks/first-time-contributor-label/test/index.js new file mode 100644 index 00000000000000..83e356bb7bca88 --- /dev/null +++ b/packages/project-management-automation/lib/tasks/first-time-contributor-label/test/index.js @@ -0,0 +1,76 @@ +/** + * Internal dependencies + */ +import firstTimeContributorLabel from '../'; + +describe( 'firstTimeContributorLabel', () => { + const payload = { + repository: { + owner: { + login: 'WordPress', + }, + name: 'gutenberg', + }, + pull_request: { + user: { + login: 'ghost', + }, + number: 123, + }, + }; + + it( 'does nothing if the user has at least one commit', async () => { + const octokit = { + repos: { + listCommits: jest.fn( () => + Promise.resolve( { + data: [ + { sha: '4c535288a6a2b75ff23ee96c75f7d9877e919241' }, + ], + } ) + ), + }, + issues: { + addLabels: jest.fn(), + }, + }; + + await firstTimeContributorLabel( payload, octokit ); + + expect( octokit.repos.listCommits ).toHaveBeenCalledWith( { + owner: 'WordPress', + repo: 'gutenberg', + author: 'ghost', + } ); + expect( octokit.issues.addLabels ).not.toHaveBeenCalled(); + } ); + + it( 'adds the First Time Contributor label if the user has no commits', async () => { + const octokit = { + repos: { + listCommits: jest.fn( () => + Promise.resolve( { + data: [], + } ) + ), + }, + issues: { + addLabels: jest.fn(), + }, + }; + + await firstTimeContributorLabel( payload, octokit ); + + expect( octokit.repos.listCommits ).toHaveBeenCalledWith( { + owner: 'WordPress', + repo: 'gutenberg', + author: 'ghost', + } ); + expect( octokit.issues.addLabels ).toHaveBeenCalledWith( { + owner: 'WordPress', + repo: 'gutenberg', + issue_number: 123, + labels: [ 'First-time Contributor' ], + } ); + } ); +} ); diff --git a/packages/project-management-automation/package.json b/packages/project-management-automation/package.json index 11e61992207d3b..fd599a0bcb4145 100644 --- a/packages/project-management-automation/package.json +++ b/packages/project-management-automation/package.json @@ -24,7 +24,7 @@ "dependencies": { "@actions/core": "^1.0.0", "@actions/github": "^1.0.0", - "@babel/runtime": "^7.11.2" + "@babel/runtime": "^7.12.5" }, "publishConfig": { "access": "public" diff --git a/packages/react-native-aztec/README.md b/packages/react-native-aztec/README.md index 737b207e8d92d9..2df9c3d276065d 100644 --- a/packages/react-native-aztec/README.md +++ b/packages/react-native-aztec/README.md @@ -236,7 +236,7 @@ RCTAztecView adds the following custom behaviours to the TextView class: Android uses a native [`ReactAztecText`](https://github.com/WordPress/gutenberg/blob/7532a485b400f86638145b71f94f6f717e5add25/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecText.java#L50) view, which extends [`AztecText`](https://github.com/wordpress-mobile/AztecEditor-Android/blob/437ecec9034003c32b9b8b0b00ec76cb5b248679/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt#L130) from the [Aztec Library for Android](https://github.com/wordpress-mobile/AztecEditor-Android). All interactions between -the native `ReactAztecText` view and the Javascript code are handled by the [`ReactAztecManager`](https://github.com/WordPress/gutenberg/blob/7532a485b400f86638145b71f94f6f717e5add25/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java#L62) +the native `ReactAztecText` view and the JavaScript code are handled by the [`ReactAztecManager`](https://github.com/WordPress/gutenberg/blob/7532a485b400f86638145b71f94f6f717e5add25/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java#L62) view manager. # License diff --git a/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java b/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java index 7d39098ae8c809..edf30a01154e86 100644 --- a/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java +++ b/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java @@ -41,11 +41,11 @@ import com.facebook.react.views.text.ReactFontManager; import com.facebook.react.views.text.ReactTextUpdate; import com.facebook.react.views.textinput.ReactContentSizeChangedEvent; -import com.facebook.react.views.textinput.ReactTextChangedEvent; import com.facebook.react.views.textinput.ReactTextInputEvent; import com.facebook.react.views.textinput.ReactTextInputManager; import com.facebook.react.views.textinput.ScrollWatcher; +import org.wordpress.aztec.Constants; import org.wordpress.aztec.formatting.LinkFormatter; import org.wordpress.aztec.glideloader.GlideImageLoader; import org.wordpress.aztec.glideloader.GlideVideoThumbnailLoader; @@ -677,7 +677,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { if (mPreviousText.length() == 0 - && !TextUtils.isEmpty(newText) + && !isTextEmpty(newText) && !TextUtils.isEmpty(mEditText.getTagName()) && mEditText.getSelectedStyles().isEmpty()) { @@ -691,6 +691,12 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { } } + // This accounts for the END_OF_BUFFER_MARKER that is added to blocks to maintain the styling, if the only char + // is the zero width marker then it is considered "empty" + private boolean isTextEmpty(String text) { + return text.length() == 0 || (text.length() == 1 && text.charAt(0) == Constants.INSTANCE.getEND_OF_BUFFER_MARKER()); + } + @Override public void afterTextChanged(Editable s) {} } diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json index 0b40fff7faf94f..f183fc014260ec 100644 --- a/packages/react-native-aztec/package.json +++ b/packages/react-native-aztec/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-aztec", - "version": "1.40.0", + "version": "1.42.1", "description": "Aztec view for react-native.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-bridge/android/build.gradle b/packages/react-native-bridge/android/build.gradle index f455e732983296..31af74444cfffc 100644 --- a/packages/react-native-bridge/android/build.gradle +++ b/packages/react-native-bridge/android/build.gradle @@ -146,7 +146,7 @@ dependencies { // For animated GIF support implementation 'com.facebook.fresco:animated-gif:2.0.0' - implementation 'com.google.android.material:material:1.1.0' + implementation 'com.google.android.material:material:1.2.1' if (rootProject.ext.buildGutenbergFromSource) { println "using gutenberg from source" @@ -305,7 +305,7 @@ If they are changed, the isBundleUpToDate flag is switched to false. That flag i } } - def nodeModulesFolders = ["$mobileGutenbergRootDir/node_modules", "$mobileGutenbergRootDir/gutenberg/node_modules"] as String[] + def nodeModulesFolders = ["$mobileGutenbergRootDir/node_modules", "$mobileGutenbergRootDir/gutenberg/node_modules", "$mobileGutenbergRootDir/jetpack/node_modules"] as String[] task cleanupNodeModulesFolders(type: Delete) { doFirst { println "Deleting node_modules folders" diff --git a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergBridgeJS2Parent.java b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergBridgeJS2Parent.java index cc5ba27356217d..f912598e0f498b 100644 --- a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergBridgeJS2Parent.java +++ b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergBridgeJS2Parent.java @@ -34,10 +34,23 @@ interface MediaUploadEventEmitter { void onMediaFileUploadFailed(int mediaId); } + interface MediaSaveEventEmitter { + void onSaveMediaFileClear(String mediaId); + void onMediaFileSaveProgress(String mediaId, float progress); + void onMediaFileSaveSucceeded(String mediaId, String mediaUrl); + void onMediaFileSaveFailed(String mediaId); + void onMediaCollectionSaveResult(String firstMediaIdInCollection, boolean success); + void onMediaIdChanged(final String oldId, final String newId, final String oldUrl); + } + interface ReplaceUnsupportedBlockCallback { void replaceUnsupportedBlock(String content, String blockId); } + interface ReplaceMediaFilesEditedBlockCallback { + void replaceMediaFilesEditedBlock(String mediaFiles, String blockId); + } + interface StarterPageTemplatesTooltipShownCallback { void onRequestStarterPageTemplatesTooltipShown(boolean tooltipShown); } @@ -70,6 +83,7 @@ enum MediaType { VIDEO("video"), MEDIA("media"), AUDIO("audio"), + ANY("any"), OTHER("other"); String name; @@ -122,6 +136,8 @@ public static GutenbergUserEvent getEnum(String eventName) { void mediaUploadSync(MediaSelectedCallback mediaSelectedCallback); + void mediaSaveSync(MediaSelectedCallback mediaSelectedCallback); + void requestImageFailedRetryDialog(int mediaId); void requestImageUploadCancelDialog(int mediaId); @@ -155,4 +171,15 @@ void gutenbergDidRequestUnsupportedBlockFallback(ReplaceUnsupportedBlockCallback void setStarterPageTemplatesTooltipShown(boolean tooltipShown); void requestStarterPageTemplatesTooltipShown(StarterPageTemplatesTooltipShownCallback starterPageTemplatesTooltipShownCallback); + + void requestMediaFilesEditorLoad(ReplaceMediaFilesEditedBlockCallback replaceMediaFilesEditedBlockCallback, + ReadableArray mediaFiles, + String blockId + ); + + void requestMediaFilesFailedRetryDialog(ReadableArray mediaFiles); + + void requestMediaFilesUploadCancelDialog(ReadableArray mediaFiles); + + void requestMediaFilesSaveCancelDialog(ReadableArray mediaFiles); } diff --git a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergWebViewActivity.java b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergWebViewActivity.java index f89df4562b42d9..cd8ffb69b7dbb0 100644 --- a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergWebViewActivity.java +++ b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergWebViewActivity.java @@ -4,7 +4,6 @@ import android.graphics.Bitmap; import android.os.Bundle; import android.os.Handler; -import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; diff --git a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNMedia.kt b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNMedia.kt index ae614d41e2cfef..8f1b3bb367e86c 100644 --- a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNMedia.kt +++ b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNMedia.kt @@ -7,5 +7,6 @@ interface RNMedia { val id: Int val type: String val caption: String + val title: String fun toMap(): WritableMap } diff --git a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgeModule.java b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgeModule.java index 16f8034435409e..4a6b844b2f5c10 100644 --- a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgeModule.java +++ b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgeModule.java @@ -50,12 +50,15 @@ public class RNReactNativeGutenbergBridgeModule extends ReactContextBaseJavaModu private static final String MAP_KEY_UPDATE_HTML = "html"; private static final String MAP_KEY_UPDATE_TITLE = "title"; + public static final String MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_NEW_ID = "newId"; private static final String MAP_KEY_SHOW_NOTICE_MESSAGE = "message"; + public static final String MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_ID = "mediaId"; public static final String MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_URL = "mediaUrl"; public static final String MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_TYPE = "mediaType"; private static final String MAP_KEY_THEME_UPDATE_COLORS = "colors"; private static final String MAP_KEY_THEME_UPDATE_GRADIENTS = "gradients"; + public static final String MAP_KEY_MEDIA_FINAL_SAVE_RESULT_SUCCESS_VALUE = "success"; private static final String MAP_KEY_IS_PREFERRED_COLOR_SCHEME_DARK = "isPreferredColorSchemeDark"; @@ -203,6 +206,11 @@ public void mediaUploadSync() { mGutenbergBridgeJS2Parent.mediaUploadSync(getNewMediaSelectedCallback(false,null)); } + @ReactMethod + public void mediaSaveSync() { + mGutenbergBridgeJS2Parent.mediaSaveSync(getNewMediaSelectedCallback(true,null)); + } + @ReactMethod public void requestImageFailedRetryDialog(final int mediaId) { mGutenbergBridgeJS2Parent.requestImageFailedRetryDialog(mediaId); @@ -228,6 +236,27 @@ public void requestMediaEditor(String mediaUrl, final Callback onUploadMediaSele mGutenbergBridgeJS2Parent.requestMediaEditor(getNewMediaSelectedCallback(false, onUploadMediaSelected), mediaUrl); } + @ReactMethod + public void requestMediaFilesEditorLoad(ReadableArray mediaFiles, String blockId) { + mGutenbergBridgeJS2Parent.requestMediaFilesEditorLoad((savedMediaFiles, savedBlockId) -> + replaceBlock(savedMediaFiles, savedBlockId), mediaFiles, blockId); + } + + @ReactMethod + public void requestMediaFilesFailedRetryDialog(ReadableArray mediaFiles) { + mGutenbergBridgeJS2Parent.requestMediaFilesFailedRetryDialog(mediaFiles); + } + + @ReactMethod + public void requestMediaFilesUploadCancelDialog(ReadableArray mediaFiles) { + mGutenbergBridgeJS2Parent.requestMediaFilesUploadCancelDialog(mediaFiles); + } + + @ReactMethod + public void requestMediaFilesSaveCancelDialog(ReadableArray mediaFiles) { + mGutenbergBridgeJS2Parent.requestMediaFilesSaveCancelDialog(mediaFiles); + } + @ReactMethod public void editorDidEmitLog(String message, int logLevel) { mGutenbergBridgeJS2Parent.editorDidEmitLog(message, GutenbergBridgeJS2Parent.LogLevel.valueOf(logLevel)); @@ -284,14 +313,12 @@ private void replaceBlock(String content, String blockId) { } private OtherMediaOptionsReceivedCallback getNewOtherMediaReceivedCallback(final Callback jsCallback) { - return new OtherMediaOptionsReceivedCallback() { - @Override public void onOtherMediaOptionsReceived(ArrayList<MediaOption> mediaOptions) { - WritableArray writableArray = new WritableNativeArray(); - for (MediaOption mediaOption : mediaOptions) { - writableArray.pushMap(mediaOption.toMap()); - } - jsCallback.invoke(writableArray); + return mediaOptions -> { + WritableArray writableArray = new WritableNativeArray(); + for (MediaOption mediaOption : mediaOptions) { + writableArray.pushMap(mediaOption.toMap()); } + jsCallback.invoke(writableArray); }; } diff --git a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/DeferredEventEmitter.java b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/DeferredEventEmitter.java index e67e76a67b098b..013a89da92f514 100644 --- a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/DeferredEventEmitter.java +++ b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/DeferredEventEmitter.java @@ -9,14 +9,17 @@ import com.facebook.react.bridge.WritableNativeMap; import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.MediaUploadEventEmitter; +import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.MediaSaveEventEmitter; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import static org.wordpress.mobile.ReactNativeGutenbergBridge.RNReactNativeGutenbergBridgeModule.MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_ID; +import static org.wordpress.mobile.ReactNativeGutenbergBridge.RNReactNativeGutenbergBridgeModule.MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_NEW_ID; import static org.wordpress.mobile.ReactNativeGutenbergBridge.RNReactNativeGutenbergBridgeModule.MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_URL; +import static org.wordpress.mobile.ReactNativeGutenbergBridge.RNReactNativeGutenbergBridgeModule.MAP_KEY_MEDIA_FINAL_SAVE_RESULT_SUCCESS_VALUE; -public class DeferredEventEmitter implements MediaUploadEventEmitter { +public class DeferredEventEmitter implements MediaUploadEventEmitter, MediaSaveEventEmitter { public interface JSEventEmitter { void emitToJS(String eventName, @Nullable WritableMap data); } @@ -26,11 +29,19 @@ public interface JSEventEmitter { private static final int MEDIA_UPLOAD_STATE_FAILED = 3; private static final int MEDIA_UPLOAD_STATE_RESET = 4; + private static final int MEDIA_SAVE_STATE_SAVING = 5; + private static final int MEDIA_SAVE_STATE_SUCCEEDED = 6; + private static final int MEDIA_SAVE_STATE_FAILED = 7; + private static final int MEDIA_SAVE_STATE_RESET = 8; + private static final int MEDIA_SAVE_FINAL_STATE_RESULT = 9; + private static final int MEDIA_SAVE_MEDIAID_CHANGED = 10; + private static final String EVENT_NAME_MEDIA_UPLOAD = "mediaUpload"; + private static final String EVENT_NAME_MEDIA_SAVE = "mediaSave"; - private static final String MAP_KEY_MEDIA_FILE_UPLOAD_STATE = "state"; - private static final String MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_PROGRESS = "progress"; - private static final String MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_SERVER_ID = "mediaServerId"; + private static final String MAP_KEY_MEDIA_FILE_STATE = "state"; + private static final String MAP_KEY_MEDIA_FILE_MEDIA_ACTION_PROGRESS = "progress"; + private static final String MAP_KEY_MEDIA_FILE_MEDIA_SERVER_ID = "mediaServerId"; private static final String MAP_KEY_UPDATE_CAPABILITIES = "updateCapabilities"; @@ -86,12 +97,12 @@ private void setMediaFileUploadDataInJS(int state, int mediaId, String mediaUrl, private void setMediaFileUploadDataInJS(int state, int mediaId, String mediaUrl, float progress, int mediaServerId) { WritableMap writableMap = new WritableNativeMap(); - writableMap.putInt(MAP_KEY_MEDIA_FILE_UPLOAD_STATE, state); + writableMap.putInt(MAP_KEY_MEDIA_FILE_STATE, state); writableMap.putInt(MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_ID, mediaId); writableMap.putString(MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_URL, mediaUrl); - writableMap.putDouble(MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_PROGRESS, progress); + writableMap.putDouble(MAP_KEY_MEDIA_FILE_MEDIA_ACTION_PROGRESS, progress); if (mediaServerId != MEDIA_SERVER_ID_UNKNOWN) { - writableMap.putInt(MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_SERVER_ID, mediaServerId); + writableMap.putInt(MAP_KEY_MEDIA_FILE_MEDIA_SERVER_ID, mediaServerId); } if (isCriticalMessage(state)) { queueActionToJS(EVENT_NAME_MEDIA_UPLOAD, writableMap); @@ -100,8 +111,36 @@ private void setMediaFileUploadDataInJS(int state, int mediaId, String mediaUrl, } } + private void setMediaSaveResultDataInJS(int state, String mediaId, String mediaUrl, float progress) { + WritableMap writableMap = new WritableNativeMap(); + writableMap.putInt(MAP_KEY_MEDIA_FILE_STATE, state); + writableMap.putString(MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_ID, mediaId); + writableMap.putString(MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_URL, mediaUrl); + writableMap.putDouble(MAP_KEY_MEDIA_FILE_MEDIA_ACTION_PROGRESS, progress); + if (isCriticalMessage(state)) { + queueActionToJS(EVENT_NAME_MEDIA_SAVE, writableMap); + } else { + emitOrDrop(EVENT_NAME_MEDIA_SAVE, writableMap); + } + } + + private void setMediaSaveResultDataInJS(int state, String mediaId, boolean success, float progress) { + WritableMap writableMap = new WritableNativeMap(); + writableMap.putInt(MAP_KEY_MEDIA_FILE_STATE, state); + writableMap.putString(MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_ID, mediaId); + writableMap.putBoolean(MAP_KEY_MEDIA_FINAL_SAVE_RESULT_SUCCESS_VALUE, success); + writableMap.putDouble(MAP_KEY_MEDIA_FILE_MEDIA_ACTION_PROGRESS, progress); + if (isCriticalMessage(state)) { + queueActionToJS(EVENT_NAME_MEDIA_SAVE, writableMap); + } else { + emitOrDrop(EVENT_NAME_MEDIA_SAVE, writableMap); + } + } + private boolean isCriticalMessage(int state) { - return state == MEDIA_UPLOAD_STATE_SUCCEEDED || state == MEDIA_UPLOAD_STATE_FAILED; + return state == MEDIA_UPLOAD_STATE_SUCCEEDED || state == MEDIA_UPLOAD_STATE_FAILED + || state == MEDIA_SAVE_STATE_SUCCEEDED || state == MEDIA_SAVE_STATE_FAILED + || state == MEDIA_SAVE_MEDIAID_CHANGED; } @Override @@ -124,6 +163,45 @@ public void onMediaFileUploadFailed(int mediaId) { setMediaFileUploadDataInJS(MEDIA_UPLOAD_STATE_FAILED, mediaId, null, 0); } + // Media file save events emitter + @Override + public void onSaveMediaFileClear(String mediaId) { + setMediaSaveResultDataInJS(MEDIA_SAVE_STATE_RESET, mediaId, null, 0); + } + + @Override + public void onMediaFileSaveProgress(String mediaId, float progress) { + setMediaSaveResultDataInJS(MEDIA_SAVE_STATE_SAVING, mediaId, null, progress); + } + + @Override + public void onMediaFileSaveSucceeded(String mediaId, String mediaUrl) { + setMediaSaveResultDataInJS(MEDIA_SAVE_STATE_SUCCEEDED, mediaId, mediaUrl, 1); + } + + @Override + public void onMediaFileSaveFailed(String mediaId) { + setMediaSaveResultDataInJS(MEDIA_SAVE_STATE_FAILED, mediaId, null, 0); + } + + @Override + public void onMediaCollectionSaveResult(String firstMediaIdInCollection, boolean success) { + setMediaSaveResultDataInJS(MEDIA_SAVE_FINAL_STATE_RESULT, firstMediaIdInCollection, success, success ? 1 : 0); + } + + @Override public void onMediaIdChanged(String oldId, String newId, String oldUrl) { + WritableMap writableMap = new WritableNativeMap(); + writableMap.putInt(MAP_KEY_MEDIA_FILE_STATE, MEDIA_SAVE_MEDIAID_CHANGED); + writableMap.putString(MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_ID, oldId); + writableMap.putString(MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_NEW_ID, newId); + writableMap.putString(MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_URL, oldUrl); + if (isCriticalMessage(MEDIA_SAVE_MEDIAID_CHANGED)) { + queueActionToJS(EVENT_NAME_MEDIA_SAVE, writableMap); + } else { + emitOrDrop(EVENT_NAME_MEDIA_SAVE, writableMap); + } + } + public void updateCapabilities(GutenbergProps gutenbergProps) { queueActionToJS(MAP_KEY_UPDATE_CAPABILITIES, Arguments.makeNativeMap(gutenbergProps.getUpdatedCapabilitiesProps())); } diff --git a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt index 6052b72b244b54..f9f65dd9c20838 100644 --- a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt +++ b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt @@ -3,6 +3,7 @@ package org.wordpress.mobile.WPAndroidGlue import android.os.Bundle data class GutenbergProps @JvmOverloads constructor( + val enableMediaFilesCollectionBlocks: Boolean, val enableMentions: Boolean, val enableUnsupportedBlockEditor: Boolean, val canEnableUnsupportedBlockEditor: Boolean, @@ -37,6 +38,7 @@ data class GutenbergProps @JvmOverloads constructor( fun getUpdatedCapabilitiesProps() = Bundle().apply { putBoolean(PROP_CAPABILITIES_MENTIONS, enableMentions) + putBoolean(PROP_CAPABILITIES_MEDIAFILES_COLLECTION_BLOCK, enableMediaFilesCollectionBlocks) putBoolean(PROP_CAPABILITIES_UNSUPPORTED_BLOCK_EDITOR, enableUnsupportedBlockEditor) putBoolean(PROP_CAPABILITIES_CAN_ENABLE_UNSUPPORTED_BLOCK_EDITOR, canEnableUnsupportedBlockEditor) putBoolean(PROP_CAPABILITIES_MODAL_LAYOUT_PICKER, isModalLayoutPickerEnabled) @@ -64,6 +66,7 @@ data class GutenbergProps @JvmOverloads constructor( private const val PROP_EDITOR_MODE_EDITOR = "editor" const val PROP_CAPABILITIES = "capabilities" + const val PROP_CAPABILITIES_MEDIAFILES_COLLECTION_BLOCK = "mediaFilesCollectionBlock" const val PROP_CAPABILITIES_MENTIONS = "mentions" const val PROP_CAPABILITIES_UNSUPPORTED_BLOCK_EDITOR = "unsupportedBlockEditor" const val PROP_CAPABILITIES_CAN_ENABLE_UNSUPPORTED_BLOCK_EDITOR = "canEnableUnsupportedBlockEditor" diff --git a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/Media.kt b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/Media.kt index f1a1f718fa6130..fc835284805ae2 100644 --- a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/Media.kt +++ b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/Media.kt @@ -13,13 +13,15 @@ data class Media( override val id: Int, override val url: String, override val type: String, - override val caption: String = "" + override val caption: String = "", + override val title: String = "" ) : RNMedia { override fun toMap(): WritableMap = WritableNativeMap().apply { putInt("id", id) putString("url", url) putString("type", type) putString("caption", caption) + putString("title", title) } companion object { @@ -28,7 +30,8 @@ data class Media( id: Int, url: String, mimeType: String?, - caption: String? + caption: String?, + title: String? ): Media { val isMediaType = { mediaType: MediaType -> mimeType?.startsWith(mediaType.name.toLowerCase(Locale.ROOT)) == true @@ -38,7 +41,7 @@ data class Media( isMediaType(VIDEO) -> VIDEO else -> OTHER }.name.toLowerCase(Locale.ROOT) - return Media(id, url, type, caption ?: "") + return Media(id, url, type, caption ?: "", title ?: "") } } } diff --git a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java index 15aaeee2960926..9c1de6ab757722 100644 --- a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java +++ b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java @@ -49,6 +49,7 @@ import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent; import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.GutenbergUserEvent; import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.MediaSelectedCallback; +import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.ReplaceMediaFilesEditedBlockCallback; import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.ReplaceUnsupportedBlockCallback; import org.wordpress.mobile.ReactNativeGutenbergBridge.RNMedia; import org.wordpress.mobile.ReactNativeGutenbergBridge.RNReactNativeGutenbergBridgePackage; @@ -82,7 +83,8 @@ public class WPAndroidGlueCode { private boolean mAppendsMultipleSelectedToSiblingBlocks = false; private OnMediaLibraryButtonListener mOnMediaLibraryButtonListener; - private OnReattachQueryListener mOnReattachQueryListener; + private OnReattachMediaUploadQueryListener mOnReattachMediaUploadQueryListener; + private OnReattachMediaSavingQueryListener mOnReattachMediaSavingQueryListener; private OnEditorMountListener mOnEditorMountListener; private OnEditorAutosaveListener mOnEditorAutosaveListener; private OnImageFullscreenPreviewListener mOnImageFullscreenPreviewListener; @@ -92,6 +94,8 @@ public class WPAndroidGlueCode { private OnGutenbergDidSendButtonPressedActionListener mOnGutenbergDidSendButtonPressedActionListener; private ReplaceUnsupportedBlockCallback mReplaceUnsupportedBlockCallback; private OnStarterPageTemplatesTooltipShownEventListener mOnStarterPageTemplatesTooltipShownListener; + private OnMediaFilesCollectionBasedBlockEditorListener mOnMediaFilesCollectionBasedBlockEditorListener; + private ReplaceMediaFilesEditedBlockCallback mReplaceMediaFilesEditedBlockCallback; private boolean mIsEditorMounted; private String mContentHtml = ""; @@ -134,6 +138,7 @@ public interface OnMediaLibraryButtonListener { void onMediaLibraryImageButtonClicked(boolean allowMultipleSelection); void onMediaLibraryVideoButtonClicked(boolean allowMultipleSelection); void onMediaLibraryMediaButtonClicked(boolean allowMultipleSelection); + void onMediaLibraryFileButtonClicked(boolean allowMultipleSelection); void onUploadPhotoButtonClicked(boolean allowMultipleSelection); void onCapturePhotoButtonClicked(); void onUploadVideoButtonClicked(boolean allowMultipleSelection); @@ -143,17 +148,29 @@ public interface OnMediaLibraryButtonListener { void onCancelUploadForMediaClicked(int mediaId); void onCancelUploadForMediaDueToDeletedBlock(int mediaId); ArrayList<MediaOption> onGetOtherMediaImageOptions(); + ArrayList<MediaOption> onGetOtherMediaFileOptions(); void onOtherMediaButtonClicked(String mediaSource, boolean allowMultipleSelection); } + public interface OnMediaFilesCollectionBasedBlockEditorListener { + void onRequestMediaFilesEditorLoad(ArrayList<Object> mediaFiles, String blockId); + void onCancelUploadForMediaCollection(ArrayList<Object> mediaFiles); + void onRetryUploadForMediaCollection(ArrayList<Object> mediaFiles); + void onCancelSaveForMediaCollection(ArrayList<Object> mediaFiles); + } + public interface OnImageFullscreenPreviewListener { void onImageFullscreenPreviewClicked(String mediaUrl); } - public interface OnReattachQueryListener { + public interface OnReattachMediaUploadQueryListener { void onQueryCurrentProgressForUploadingMedia(); } + public interface OnReattachMediaSavingQueryListener { + void onQueryCurrentProgressForSavingMedia(); + } + public interface OnEditorMountListener { void onEditorDidMount(ArrayList<Object> unsupportedBlockNames); } @@ -228,16 +245,19 @@ public void requestMediaPickFromMediaLibrary(MediaSelectedCallback mediaSelected mOnMediaLibraryButtonListener.onMediaLibraryVideoButtonClicked(allowMultipleSelection); } else if (mediaType == MediaType.MEDIA) { mOnMediaLibraryButtonListener.onMediaLibraryMediaButtonClicked(allowMultipleSelection); + } else if (mediaType == MediaType.ANY) { + mOnMediaLibraryButtonListener.onMediaLibraryFileButtonClicked(allowMultipleSelection); } } @Override public void requestMediaPickFromDeviceLibrary(MediaSelectedCallback mediaSelectedCallback, Boolean allowMultipleSelection, MediaType mediaType) { mMediaPickedByUserOnBlock = true; - mAppendsMultipleSelectedToSiblingBlocks = false; + // image blocks do not respect the multiple selection flag, so we set the append as siblings flag instead + mAppendsMultipleSelectedToSiblingBlocks = mediaType == MediaType.IMAGE && !allowMultipleSelection; mMediaSelectedCallback = mediaSelectedCallback; if (mediaType == MediaType.IMAGE) { - mOnMediaLibraryButtonListener.onUploadPhotoButtonClicked(allowMultipleSelection); + mOnMediaLibraryButtonListener.onUploadPhotoButtonClicked(true); } else if (mediaType == MediaType.VIDEO) { mOnMediaLibraryButtonListener.onUploadVideoButtonClicked(allowMultipleSelection); } else if (mediaType == MediaType.MEDIA) { @@ -266,7 +286,13 @@ public void requestMediaImport(String url, MediaSelectedCallback mediaSelectedCa @Override public void mediaUploadSync(MediaSelectedCallback mediaSelectedCallback) { mMediaSelectedCallback = mediaSelectedCallback; - mOnReattachQueryListener.onQueryCurrentProgressForUploadingMedia(); + mOnReattachMediaUploadQueryListener.onQueryCurrentProgressForUploadingMedia(); + } + + @Override + public void mediaSaveSync(MediaSelectedCallback mediaSelectedCallback) { + mMediaSelectedCallback = mediaSelectedCallback; + mOnReattachMediaSavingQueryListener.onQueryCurrentProgressForSavingMedia(); } @Override @@ -330,8 +356,9 @@ public void getOtherMediaPickerOptions(OtherMediaOptionsReceivedCallback otherMe if (mediaType == MediaType.IMAGE || mediaType == MediaType.MEDIA) { ArrayList<MediaOption> otherMediaImageOptions = mOnMediaLibraryButtonListener.onGetOtherMediaImageOptions(); otherMediaOptionsReceivedCallback.onOtherMediaOptionsReceived(otherMediaImageOptions); - } else { - otherMediaOptionsReceivedCallback.onOtherMediaOptionsReceived(new ArrayList<MediaOption>()); + } else if (mediaType == MediaType.ANY) { + ArrayList<MediaOption> otherMediaFileOptions = mOnMediaLibraryButtonListener.onGetOtherMediaFileOptions(); + otherMediaOptionsReceivedCallback.onOtherMediaOptionsReceived(otherMediaFileOptions); } } @@ -398,6 +425,38 @@ public void requestStarterPageTemplatesTooltipShown(StarterPageTemplatesTooltipS boolean tooltipShown = mOnStarterPageTemplatesTooltipShownListener.onRequestStarterPageTemplatesTooltipShown(); starterPageTemplatesTooltipShownCallback.onRequestStarterPageTemplatesTooltipShown(tooltipShown); } + + @Override + public void requestMediaFilesEditorLoad( + ReplaceMediaFilesEditedBlockCallback replaceMediaFilesEditedBlockCallback, + ReadableArray mediaFiles, + String blockId + ) { + mReplaceMediaFilesEditedBlockCallback = replaceMediaFilesEditedBlockCallback; + mOnMediaFilesCollectionBasedBlockEditorListener + .onRequestMediaFilesEditorLoad(mediaFiles.toArrayList(), blockId); + } + + @Override + public void requestMediaFilesFailedRetryDialog(ReadableArray mediaFiles) { + mOnMediaFilesCollectionBasedBlockEditorListener.onRetryUploadForMediaCollection( + mediaFiles.toArrayList() + ); + } + + @Override + public void requestMediaFilesUploadCancelDialog(ReadableArray mediaFiles) { + mOnMediaFilesCollectionBasedBlockEditorListener.onCancelUploadForMediaCollection( + mediaFiles.toArrayList() + ); + } + + @Override + public void requestMediaFilesSaveCancelDialog(ReadableArray mediaFiles) { + mOnMediaFilesCollectionBasedBlockEditorListener.onCancelSaveForMediaCollection( + mediaFiles.toArrayList() + ); + } }, mIsDarkMode); return Arrays.asList( @@ -461,7 +520,8 @@ public void onCreateView(Context initContext, public void attachToContainer(ViewGroup viewGroup, OnMediaLibraryButtonListener onMediaLibraryButtonListener, - OnReattachQueryListener onReattachQueryListener, + OnReattachMediaUploadQueryListener onReattachMediaUploadQueryListener, + OnReattachMediaSavingQueryListener onReattachMediaSavingQueryListener, OnEditorMountListener onEditorMountListener, OnEditorAutosaveListener onEditorAutosaveListener, OnAuthHeaderRequestedListener onAuthHeaderRequestedListener, @@ -473,12 +533,14 @@ public void attachToContainer(ViewGroup viewGroup, OnGutenbergDidSendButtonPressedActionListener onGutenbergDidSendButtonPressedActionListener, AddMentionUtil addMentionUtil, OnStarterPageTemplatesTooltipShownEventListener onStarterPageTemplatesTooltipListener, + OnMediaFilesCollectionBasedBlockEditorListener onMediaFilesCollectionBasedBlockEditorListener, boolean isDarkMode) { MutableContextWrapper contextWrapper = (MutableContextWrapper) mReactRootView.getContext(); contextWrapper.setBaseContext(viewGroup.getContext()); mOnMediaLibraryButtonListener = onMediaLibraryButtonListener; - mOnReattachQueryListener = onReattachQueryListener; + mOnReattachMediaUploadQueryListener = onReattachMediaUploadQueryListener; + mOnReattachMediaSavingQueryListener = onReattachMediaSavingQueryListener; mOnEditorMountListener = onEditorMountListener; mOnEditorAutosaveListener = onEditorAutosaveListener; mRequestExecutor = fetchExecutor; @@ -489,6 +551,7 @@ public void attachToContainer(ViewGroup viewGroup, mOnGutenbergDidSendButtonPressedActionListener = onGutenbergDidSendButtonPressedActionListener; mAddMentionUtil = addMentionUtil; mOnStarterPageTemplatesTooltipShownListener = onStarterPageTemplatesTooltipListener; + mOnMediaFilesCollectionBasedBlockEditorListener = onMediaFilesCollectionBasedBlockEditorListener; sAddCookiesInterceptor.setOnAuthHeaderRequestedListener(onAuthHeaderRequestedListener); @@ -856,6 +919,30 @@ public void clearMediaFileURL(final int mediaId) { mDeferredEventEmitter.onUploadMediaFileClear(mediaId); } + public void clearFileSaveStatus(final String mediaId) { + mDeferredEventEmitter.onSaveMediaFileClear(mediaId); + } + + public void mediaFileSaveProgress(final String mediaId, final float progress) { + mDeferredEventEmitter.onMediaFileSaveProgress(mediaId, progress); + } + + public void mediaFileSaveFailed(final String mediaId) { + mDeferredEventEmitter.onMediaFileSaveFailed(mediaId); + } + + public void mediaFileSaveSucceeded(final String mediaId, final String mediaUrl) { + mDeferredEventEmitter.onMediaFileSaveSucceeded(mediaId, mediaUrl); + } + + public void mediaCollectionFinalSaveResult(final String blockFirstMediaId, final boolean success) { + mDeferredEventEmitter.onMediaCollectionSaveResult(blockFirstMediaId, success); + } + + public void mediaIdChanged(final String oldId, final String newId, final String oldUrl) { + mDeferredEventEmitter.onMediaIdChanged(oldId, newId, oldUrl); + } + public void replaceUnsupportedBlock(String content, String blockId) { if (mReplaceUnsupportedBlockCallback != null) { mReplaceUnsupportedBlockCallback.replaceUnsupportedBlock(content, blockId); @@ -863,6 +950,13 @@ public void replaceUnsupportedBlock(String content, String blockId) { } } + public void replaceMediaFilesEditedBlock(String mediaFiles, String blockId) { + if (mReplaceMediaFilesEditedBlockCallback != null) { + mReplaceMediaFilesEditedBlockCallback.replaceMediaFilesEditedBlock(mediaFiles, blockId); + mReplaceMediaFilesEditedBlockCallback = null; + } + } + private boolean isMediaSelectedCallbackRegistered() { return mMediaSelectedCallback != null; } diff --git a/packages/react-native-bridge/index.js b/packages/react-native-bridge/index.js index 84122ec16bbe31..f7d76c91d53cd5 100644 --- a/packages/react-native-bridge/index.js +++ b/packages/react-native-bridge/index.js @@ -67,10 +67,50 @@ export function subscribeUpdateHtml( callback ) { return gutenbergBridgeEvents.addListener( 'updateHtml', callback ); } +/** + * Request to subscribe to mediaUpload events + * + * When a media item exists as a local file and is to be uploaded, these are the generated events that are useful listening to. + * see subscribeMediaSave for events during a save operation. + * + * @param {Function} callback RN Callback function to be called with the following + * state and params: + * state: + * MEDIA_UPLOAD_STATE_SAVING: this is a progress update. Takes String mediaId, float progress. + * MEDIA_UPLOAD_STATE_SUCCEEDED: sent when one media is finished being saved. Takes String mediaId, String mediaUrl, String serverID + * (which is the remote id assigned to this file after having been uploaded). + * MEDIA_UPLOAD_STATE_FAILED: sent in case of saving failure (final state). Takes String mediaId. + * MEDIA_UPLOAD_STATE_RESET: sent when the progress and state needs be reset (a retry for example, for cleanup). Takes String mediaId. + */ export function subscribeMediaUpload( callback ) { return gutenbergBridgeEvents.addListener( 'mediaUpload', callback ); } +/** + * Request to subscribe to mediaSave events + * + * When a media item does not yet exist as a local file and is progressively being saved, these are the generated events that are useful listening to. + * see subscribeMediaUpload for events during an upload operation. + * + * @param {Function} callback RN Callback function to be called with the following + * state and params: + * Note that the first 4 states described are similar to upload events. + * state: + * MEDIA_SAVE_STATE_SAVING: this is a progress update. Takes String mediaId, float progress. + * MEDIA_SAVE_STATE_SUCCEEDED: sent when one media is finished being saved. Takes String mediaId, String mediaUrl. + * MEDIA_SAVE_STATE_FAILED: sent in case of saving failure (final state). Takes String mediaId. + * MEDIA_SAVE_STATE_RESET: sent when the progress and state needs be reset (a retry for example, for cleanup). Takes String mediaId. + * MEDIA_SAVE_FINAL_STATE_RESULT: used in media collections, sent when ALL media items in a collection have reached + * a final state (either FAILED or SUCCEEDED). Handy to know when to show a final state to the user, on + * a media collection based block when we don't know if there are still events to be received for other + * items in the collection. + * MEDIA_SAVE_MEDIAID_CHANGED: used when changing a media item id from a temporary id to a local file id, and then from a local file + * id to a remote file id. + */ +export function subscribeMediaSave( callback ) { + return gutenbergBridgeEvents.addListener( 'mediaSave', callback ); +} + export function subscribeMediaAppend( callback ) { return gutenbergBridgeEvents.addListener( 'mediaAppend', callback ); } @@ -158,11 +198,6 @@ export function requestUnsupportedBlockFallback( ); } -/** - * Messages the client that an action button was pressed. - * - * @param {string} htmlContent One of the values deffined on `actionButtons` constant object. - */ export function sendActionButtonPressedAction( buttonType ) { RNReactNativeGutenbergBridge.actionButtonPressed( buttonType ); } @@ -171,10 +206,26 @@ export function requestMediaImport( url, callback ) { return RNReactNativeGutenbergBridge.requestMediaImport( url, callback ); } +/** + * Request to start listening to upload events when in-progress uploads are in place + * + * For example, when media is being uploaded and the user re-enters the editor + * + */ export function mediaUploadSync() { return RNReactNativeGutenbergBridge.mediaUploadSync(); } +/** + * Request to start listening to save events when in-progress saves are in place + * + * For example, when media is being saved and the user re-enters the editor + * + */ +export function mediaSaveSync() { + return RNReactNativeGutenbergBridge.mediaSaveSync(); +} + export function requestImageFailedRetryDialog( mediaId ) { return RNReactNativeGutenbergBridge.requestImageFailedRetryDialog( mediaId @@ -244,4 +295,63 @@ export function setStarterPageTemplatesTooltipShown( tooltipShown ) { ); } +/** + * Request the host app to show the block for editing its mediaFiles collection + * + * For example, a mediaFiles collection editor can make special handling of visualization + * in this regard. + * + * @param {Array<Map>} mediaFiles the mediaFiles attribute of the block, containing data about each media item. + * @param {string} blockClientId the clientId of the block. + */ +export function requestMediaFilesEditorLoad( mediaFiles, blockClientId ) { + RNReactNativeGutenbergBridge.requestMediaFilesEditorLoad( + mediaFiles, + blockClientId + ); +} + +/** + * Request the host app to show a retry dialog for mediaFiles arrays which contained items that failed + * to upload + * + * For example, tapping on a failed-media overlay would trigger this request and a "Retry?" dialog + * would be presented to the user + * + * @param {Array<Map>} mediaFiles the mediaFiles attribute of the block, containing data about each media item + */ +export function requestMediaFilesFailedRetryDialog( mediaFiles ) { + RNReactNativeGutenbergBridge.requestMediaFilesFailedRetryDialog( + mediaFiles + ); +} + +/** + * Request the host app to show a cancel dialog for mediaFiles arrays currently being uploaded + * + * For example, tapping on a block containing mediaFiles that are currently being uplaoded would trigger this request + * and a "Cancel upload?" dialog would be presented to the user. + * + * @param {Array<Map>} mediaFiles the mediaFiles attribute of the block, containing data about each media item + */ +export function requestMediaFilesUploadCancelDialog( mediaFiles ) { + RNReactNativeGutenbergBridge.requestMediaFilesUploadCancelDialog( + mediaFiles + ); +} + +/** + * Request the host app to show a cancel dialog for mediaFiles arrays currently undergoing a save operation + * + * Save operations on mediaFiles collection could be lengthy so for example, tapping on a mediaFiles-type block + * currently being saved would trigger this request and a "Cancel save?" dialog would be presented to the user + * + * @param {Array<Map>} mediaFiles the mediaFiles attribute of the block, containing data about each media item. + */ +export function requestMediaFilesSaveCancelDialog( mediaFiles ) { + RNReactNativeGutenbergBridge.requestMediaFilesSaveCancelDialog( + mediaFiles + ); +} + export default RNReactNativeGutenbergBridge; diff --git a/packages/react-native-bridge/ios/Gutenberg.swift b/packages/react-native-bridge/ios/Gutenberg.swift index c8bf028e88705e..b52699d7ce921c 100644 --- a/packages/react-native-bridge/ios/Gutenberg.swift +++ b/packages/react-native-bridge/ios/Gutenberg.swift @@ -141,8 +141,33 @@ public class Gutenberg: NSObject { private func sendEvent(_ event: RNReactNativeGutenbergBridge.EventName, body: [String: Any]? = nil) { bridgeModule.sendEvent(withName: event.rawValue, body: body) } - + public func mediaUploadUpdate(id: Int32, state: MediaUploadState, progress: Float, url: URL?, serverID: Int32?) { + mediaUpdate(event: .mediaUpload, id: id, state: state, progress: progress, url: url, serverID: serverID) + } + + public func updateMediaSaveStatus(id: Int32, state: MediaSaveState, progress: Float, url: URL?, serverID: Int32?) { + mediaUpdate(event: .mediaSave, id: id, state: state, progress: progress, url: url, serverID: serverID) + } + + public func onMediaCollectionSaveResult(firstMediaIdInCollection: String, success: Bool) { + sendEvent(.mediaSave, body: [ + "state": MediaSaveEvent.result.rawValue, + "firstMediaIdInCollection": firstMediaIdInCollection, + "success": success, + ]) + } + + public func onMediaIdChanged(oldId: String, newId: String, oldUrl: URL) { + sendEvent(.mediaSave, body: [ + "state": MediaSaveEvent.idChange.rawValue, + "oldId": oldId, + "newId": newId, + "oldUrl": oldUrl, + ]) + } + + private func mediaUpdate<State: MediaState>(event: RNReactNativeGutenbergBridge.EventName, id: Int32, state: State, progress: Float, url: URL?, serverID: Int32?) { var data: [String: Any] = ["mediaId": id, "state": state.rawValue, "progress": progress]; if let url = url { data["mediaUrl"] = url.absoluteString @@ -150,7 +175,7 @@ public class Gutenberg: NSObject { if let serverID = serverID { data["mediaServerId"] = serverID } - sendEvent(.mediaUpload, body: data) + sendEvent(event, body: data) } public func appendMedia(id: Int32, url: URL, type: MediaType) { @@ -204,14 +229,27 @@ extension Gutenberg: RCTBridgeDelegate { } } +protocol MediaState: RawRepresentable {} + extension Gutenberg { - public enum MediaUploadState: Int { + public enum MediaUploadState: Int, MediaState { case uploading = 1 case succeeded = 2 case failed = 3 case reset = 4 } - + + public enum MediaSaveState: Int, MediaState { + case saving = 5 + case succeeded = 6 + case failed = 7 + case reset = 8 + } + + enum MediaSaveEvent: Int { + case result = 9 + case idChange = 10 + } } extension Gutenberg { @@ -220,6 +258,7 @@ extension Gutenberg { case video case audio case other + case any } } diff --git a/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift b/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift index 7f2b91db3a76ab..2e624791346908 100644 --- a/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift +++ b/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift @@ -1,19 +1,22 @@ -public struct MediaInfo { +public struct MediaInfo: Encodable { public let id: Int32? public let url: String? public let type: String? + public let title: String? public let caption: String? - public init(id: Int32?, url: String?, type: String?, caption: String? = nil) { + public init(id: Int32?, url: String?, type: String?, caption: String? = nil, title: String? = nil) { self.id = id self.url = url self.type = type self.caption = caption + self.title = title } } /// Definition of capabilities to enable in the Block Editor public enum Capabilities: String { + case mediaFilesCollectionBlock case mentions case unsupportedBlockEditor case canEnableUnsupportedBlockEditor @@ -117,10 +120,10 @@ extension RCTLogLevel { } public enum GutenbergUserEvent { - + case editorSessionTemplateApply(_ template: String) case editorSessionTemplatePreview(_ template: String) - + init?(event: String, properties:[AnyHashable: Any]?) { switch event { case "editor_session_template_apply": @@ -210,7 +213,7 @@ public protocol GutenbergBridgeDelegate: class { /// Tells the delegate to display the media editor from a given URL /// func gutenbergDidRequestMediaEditor(with mediaUrl: URL, callback: @escaping MediaPickerDidPickMediaCallback) - + /// Tells the delegate that the editor needs to log a custom event /// - Parameter event: The event key to be logged func gutenbergDidLogUserEvent(_ event: GutenbergUserEvent) @@ -224,12 +227,26 @@ public protocol GutenbergBridgeDelegate: class { /// Tells the delegate that the editor requested to show the tooltip func gutenbergDidRequestStarterPageTemplatesTooltipShown() -> Bool - + /// Tells the delegate that the editor requested to set the tooltip's visibility - /// - Parameter tooltipShown: Tooltip's visibility value + /// - Parameter tooltipShown: Tooltip's visibility value func gutenbergDidRequestSetStarterPageTemplatesTooltipShown(_ tooltipShown: Bool) func gutenbergDidSendButtonPressedAction(_ buttonType: Gutenberg.ActionButtonType) + + // Media Collection + + /// Tells the delegate that a media collection block requested to reconnect with media save coordinator. + /// + func gutenbergDidRequestMediaSaveSync() + + func gutenbergDidRequestMediaFilesEditorLoad(_ mediaFiles: [String], blockId: String) + + func gutenbergDidRequestMediaFilesFailedRetryDialog(_ mediaFiles: [String]) + + func gutenbergDidRequestMediaFilesUploadCancelDialog(_ mediaFiles: [String]) + + func gutenbergDidRequestMediaFilesSaveCancelDialog(_ mediaFiles: [String]) } // MARK: - Optional GutenbergBridgeDelegate methods @@ -239,4 +256,12 @@ public extension GutenbergBridgeDelegate { func gutenbergDidLayout() { } func gutenbergDidRequestUnsupportedBlockFallback(for block: Block) { } func gutenbergDidSendButtonPressedAction(_ buttonType: Gutenberg.ActionButtonType) { } + + // Media Collection + + func gutenbergDidRequestMediaSaveSync() {} + func gutenbergDidRequestMediaFilesEditorLoad(_ mediaFiles: [String], blockId: String) { } + func gutenbergDidRequestMediaFilesFailedRetryDialog(_ mediaFiles: [String]) { } + func gutenbergDidRequestMediaFilesUploadCancelDialog(_ mediaFiles: [String]) { } + func gutenbergDidRequestMediaFilesSaveCancelDialog(_ mediaFiles: [String]) { } } diff --git a/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.m b/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.m index 9b68bf074f161d..aafdce5c869aa0 100644 --- a/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.m +++ b/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.m @@ -23,6 +23,12 @@ @interface RCT_EXTERN_MODULE(RNReactNativeGutenbergBridge, NSObject) RCT_EXTERN_METHOD(addMention:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)rejecter) RCT_EXTERN_METHOD(requestStarterPageTemplatesTooltipShown:(RCTResponseSenderBlock)callback) RCT_EXTERN_METHOD(setStarterPageTemplatesTooltipShown:(BOOL)tooltipShown) +RCT_EXTERN_METHOD(requestMediaFilesEditorLoad:(NSArray<NSString *> *)mediaFiles blockId:(NSString *)blockId) +RCT_EXTERN_METHOD(requestMediaFilesFailedRetryDialog:(NSArray<NSString *> *)mediaFiles) +RCT_EXTERN_METHOD(requestMediaFilesUploadCancelDialog:(NSArray<NSString *> *)mediaFiles) +RCT_EXTERN_METHOD(requestMediaFilesSaveCancelDialog:(NSArray<NSString *> *)mediaFiles) +RCT_EXTERN_METHOD(onCancelUploadForMediaCollection:(NSArray<NSString *> *)mediaFiles) RCT_EXTERN_METHOD(actionButtonPressed:(NSString *)buttonType) +RCT_EXTERN_METHOD(mediaSaveSync) @end diff --git a/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.swift b/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.swift index 520b911a27e09e..3d8168ba17e8d4 100644 --- a/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.swift +++ b/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.swift @@ -288,6 +288,43 @@ public class RNReactNativeGutenbergBridge: RCTEventEmitter { self.delegate?.gutenbergDidRequestSetStarterPageTemplatesTooltipShown(tooltipShown) } + @objc + func requestMediaFilesEditorLoad(_ mediaFiles: [String], blockId: String) { + DispatchQueue.main.async { + self.delegate?.gutenbergDidRequestMediaFilesEditorLoad(mediaFiles, blockId: blockId) + } + } + + @objc + func requestMediaFilesFailedRetryDialog(_ mediaFiles: [String]) { + DispatchQueue.main.async { + self.delegate?.gutenbergDidRequestMediaFilesFailedRetryDialog(mediaFiles) + } + } + + @objc + func requestMediaFilesUploadCancelDialog(_ mediaFiles: [String]) { + DispatchQueue.main.async { + self.delegate?.gutenbergDidRequestMediaFilesUploadCancelDialog(mediaFiles) + } + } + + @objc + func requestMediaFilesSaveCancelDialog(_ mediaFiles: [String]) { + DispatchQueue.main.async { + self.delegate?.gutenbergDidRequestMediaFilesSaveCancelDialog(mediaFiles) + } + } + + @objc + func mediaSaveSync() { + DispatchQueue.main.async { + if self.hasObservers { + self.delegate?.gutenbergDidRequestMediaSaveSync() + } + } + } + @objc func actionButtonPressed(_ buttonType: String) { guard let button = Gutenberg.ActionButtonType(rawValue: buttonType) else { @@ -321,6 +358,7 @@ extension RNReactNativeGutenbergBridge { case replaceBlock case updateCapabilities case showNotice + case mediaSave } public override func supportedEvents() -> [String]! { @@ -361,23 +399,17 @@ extension RNReactNativeGutenbergBridge { } } -extension RNReactNativeGutenbergBridge { - enum MediaKey { - static let id = "id" - static let url = "url" - static let type = "type" - static let caption = "caption" - } -} - extension MediaInfo { - + /// Dynamically wraps up all properties into a Json Object to be sent to JS Side. func encodeForJS() -> [String: Any] { - return [ - RNReactNativeGutenbergBridge.MediaKey.id: id as Any, - RNReactNativeGutenbergBridge.MediaKey.url: url as Any, - RNReactNativeGutenbergBridge.MediaKey.type: type as Any, - RNReactNativeGutenbergBridge.MediaKey.caption: caption as Any - ] + guard + let data = try? JSONEncoder().encode(self), + let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else + { + assertionFailure("Encoding of MediaInfo failed") + return [String: Any]() + } + + return jsonObject } } diff --git a/packages/react-native-bridge/package.json b/packages/react-native-bridge/package.json index 32af4dfee30956..32742c13d9cc59 100644 --- a/packages/react-native-bridge/package.json +++ b/packages/react-native-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-bridge", - "version": "1.40.0", + "version": "1.42.1", "description": "Native bridge library used to integrate the block editor into a native App.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 31b6c9261c2370..155780077c51ab 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -11,6 +11,23 @@ For each user feature we should also add a importance categorization label to i ## Unreleased +* [***] Adding support for selecting different unit of value in Cover and Columns blocks [#26161] +* [**] Button block - Add link picker to the block settings [#26206] +* [**] Support to render background/text colors in Group, Paragraph and Quote blocks [#25994] +* [*] Fix theme colors syncing with the editor [#26821] +* [**] Fix issue where a blocks would disappear when deleting all of the text inside without requiring the extra backspace to remove the block. [#27583] + +## 1.41.0 + +* [***] Faster editor start and overall operation on Android [#26732] +* [*] [Android] Enable multiple upload support for Image block + +## 1.40.0 + +## 1.39.1 + +* [*] Heading block - Disable full-width/wide alignment [#26308] + ## 1.39.0 * [***] Full-width and wide alignment support for Video, Latest-posts, Gallery, Media & text, and Pullquote block diff --git a/packages/react-native-editor/__device-tests__/.eslintrc.js b/packages/react-native-editor/__device-tests__/.eslintrc.js new file mode 100644 index 00000000000000..8be64e4c826a0b --- /dev/null +++ b/packages/react-native-editor/__device-tests__/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + extends: '../.eslintrc.js', + globals: { + editorPage: true, // Defined in 'jest_ui_test_environment.js' + }, +}; diff --git a/packages/react-native-editor/__device-tests__/README.md b/packages/react-native-editor/__device-tests__/README.md index 0d7a11cbd38be1..030f2947dc744a 100644 --- a/packages/react-native-editor/__device-tests__/README.md +++ b/packages/react-native-editor/__device-tests__/README.md @@ -67,4 +67,4 @@ After the build is complete, an appium server is fired up on port 4723 and the d ----- -To read more about writing your own tests please read the [contributing guide](https://github.com/wordpress-mobile/gutenberg-mobile/blob/develop/__device-tests__/CONTRIBUTING.md) +To read more about writing your own tests please read the [contributing guide](https://github.com/WordPress/gutenberg/blob/master/packages/react-native-editor/__device-tests__/CONTRIBUTING.md) diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion.test.js index 2d8ce89ffda3ef..8721f11c793554 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion.test.js @@ -1,50 +1,15 @@ /** * Internal dependencies */ -import EditorPage from './pages/editor-page'; -import { - setupDriver, - isLocalEnvironment, - stopDriver, - isAndroid, - swipeDown, - clickMiddleOfElement, -} from './helpers/utils'; +import { blockNames } from './pages/editor-page'; +import { isAndroid, swipeDown, clickMiddleOfElement } from './helpers/utils'; import testData from './helpers/test-data'; -jest.setTimeout( 1000000 ); - describe( 'Gutenberg Editor tests for Block insertion', () => { - let driver; - let editorPage; - let allPassed = true; - const paragraphBlockName = 'Paragraph'; - - // Use reporter for setting status for saucelabs Job - if ( ! isLocalEnvironment() ) { - const reporter = { - specDone: async ( result ) => { - allPassed = allPassed && result.status !== 'failed'; - }, - }; - - jasmine.getEnv().addReporter( reporter ); - } - - beforeAll( async () => { - driver = await setupDriver(); - editorPage = new EditorPage( driver ); - } ); - - it( 'should be able to see visual editor', async () => { - // wait for the block editor to load - await expect( editorPage.getBlockList() ).resolves.toBe( true ); - } ); - it( 'should be able to insert block into post', async () => { - await editorPage.addNewBlock( paragraphBlockName ); + await editorPage.addNewBlock( blockNames.paragraph ); let paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName + blockNames.paragraph ); if ( isAndroid() ) { await paragraphBlockElement.click(); @@ -54,29 +19,33 @@ describe( 'Gutenberg Editor tests for Block insertion', () => { // Should have 3 paragraph blocks at this point paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName, + blockNames.paragraph, 2 ); await paragraphBlockElement.click(); - await editorPage.addNewBlock( paragraphBlockName ); + await editorPage.addNewBlock( blockNames.paragraph ); paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName, + blockNames.paragraph, 3 ); await paragraphBlockElement.click(); await editorPage.sendTextToParagraphBlock( 3, testData.mediumText ); - await editorPage.verifyHtmlContent( testData.blockInsertionHtml ); + const html = await editorPage.getHtmlContent(); + + expect( testData.blockInsertionHtml.toLowerCase() ).toBe( + html.toLowerCase() + ); // wait for the block editor to load and for accessibility ids to update - await driver.sleep( 3000 ); + await editorPage.driver.sleep( 3000 ); // Workaround for now since deleting the first element causes a crash on CI for Android if ( isAndroid() ) { paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName, + blockNames.paragraph, 3, { autoscroll: true, @@ -84,37 +53,43 @@ describe( 'Gutenberg Editor tests for Block insertion', () => { ); await paragraphBlockElement.click(); - await editorPage.removeBlockAtPosition( paragraphBlockName, 3 ); + await editorPage.removeBlockAtPosition( blockNames.paragraph, 3 ); for ( let i = 3; i > 0; i-- ) { // wait for accessibility ids to update - await driver.sleep( 1000 ); + await editorPage.driver.sleep( 1000 ); paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName, + blockNames.paragraph, i, { autoscroll: true, } ); await paragraphBlockElement.click(); - await editorPage.removeBlockAtPosition( paragraphBlockName, i ); + await editorPage.removeBlockAtPosition( + blockNames.paragraph, + i + ); } } else { for ( let i = 4; i > 0; i-- ) { // wait for accessibility ids to update - await driver.sleep( 1000 ); + await editorPage.driver.sleep( 1000 ); paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName + blockNames.paragraph + ); + await clickMiddleOfElement( + editorPage.driver, + paragraphBlockElement ); - await clickMiddleOfElement( driver, paragraphBlockElement ); - await editorPage.removeBlockAtPosition( paragraphBlockName ); + await editorPage.removeBlockAtPosition( blockNames.paragraph ); } } } ); it( 'should be able to insert block at the beginning of post from the title', async () => { - await editorPage.addNewBlock( paragraphBlockName ); + await editorPage.addNewBlock( blockNames.paragraph ); let paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName + blockNames.paragraph ); if ( isAndroid() ) { await paragraphBlockElement.click(); @@ -127,29 +102,24 @@ describe( 'Gutenberg Editor tests for Block insertion', () => { await editorPage.dismissKeyboard(); } - await swipeDown( driver ); + await swipeDown( editorPage.driver ); const titleElement = await editorPage.getTitleElement( { autoscroll: true, } ); await titleElement.click(); await titleElement.click(); - await editorPage.addNewBlock( paragraphBlockName ); + await editorPage.addNewBlock( blockNames.paragraph ); paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName + blockNames.paragraph ); - await clickMiddleOfElement( driver, paragraphBlockElement ); + await clickMiddleOfElement( editorPage.driver, paragraphBlockElement ); await editorPage.sendTextToParagraphBlock( 1, testData.mediumText ); await paragraphBlockElement.click(); - await editorPage.verifyHtmlContent( - testData.blockInsertionHtmlFromTitle - ); - } ); + const html = await editorPage.getHtmlContent(); - afterAll( async () => { - if ( ! isLocalEnvironment() ) { - driver.sauceJobStatus( allPassed ); - } - await stopDriver( driver ); + expect( testData.blockInsertionHtmlFromTitle.toLowerCase() ).toBe( + html.toLowerCase() + ); } ); } ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-columns.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-columns.test.js new file mode 100644 index 00000000000000..b57ea2588d7ee6 --- /dev/null +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-columns.test.js @@ -0,0 +1,19 @@ +/** + * Internal dependencies + */ +import { blockNames } from './pages/editor-page'; +import testData from './helpers/test-data'; + +describe( 'Gutenberg Editor Columns Block test', () => { + it( 'should be able to handle a columns width unit from web', async () => { + await editorPage.setHtmlContent( + testData.columnsWithDifferentUnitsHtml + ); + + const columnsBlock = await editorPage.getFirstBlockVisible(); + await columnsBlock.click(); + + expect( columnsBlock ).toBeTruthy(); + await editorPage.removeBlockAtPosition( blockNames.columns ); + } ); +} ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-cover.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-cover.test.js new file mode 100644 index 00000000000000..9ae321e3b39820 --- /dev/null +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-cover.test.js @@ -0,0 +1,30 @@ +/** + * Internal dependencies + */ +import { blockNames } from './pages/editor-page'; +import { isAndroid } from './helpers/utils'; +import testData from './helpers/test-data'; + +describe( 'Gutenberg Editor Cover Block test', () => { + it( 'should displayed properly and have properly converted height (ios only)', async () => { + await editorPage.setHtmlContent( testData.coverHeightWithRemUnit ); + + const coverBlock = await editorPage.getBlockAtPosition( + blockNames.cover + ); + + // Temporarily this test is skipped on Android,due to the inconsistency of the results, + // which are related to getting values in raw pixels instead of density pixels on Android. + if ( ! isAndroid() ) { + const { height } = await coverBlock.getSize(); + // Height is set to 20rem, where 1rem is 16. + // There is also block's vertical padding equal 32. + // Finally, the total height should be 20 * 16 + 32 = 352 + expect( height ).toBe( 352 ); + } + + await coverBlock.click(); + expect( coverBlock ).toBeTruthy(); + await editorPage.removeBlockAtPosition( blockNames.cover ); + } ); +} ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-file.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-file.test.js new file mode 100644 index 00000000000000..040e93d8ac2587 --- /dev/null +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-file.test.js @@ -0,0 +1,26 @@ +/** + * Internal dependencies + */ +import { blockNames } from './pages/editor-page'; +import testData from './helpers/test-data'; + +describe( 'Gutenberg Editor File Block tests @canary', () => { + it( 'should be able to add a file block', async () => { + await editorPage.addNewBlock( blockNames.file ); + const block = await editorPage.getFirstBlockVisible(); + await expect( block ).toBeTruthy(); + } ); + + it( 'should add a file to the block ', async () => { + const block = await editorPage.getFirstBlockVisible(); + + block.click(); + await editorPage.driver.sleep( 1000 ); + await editorPage.chooseMediaLibrary(); + + const html = await editorPage.getHtmlContent(); + expect( testData.fileBlockPlaceholder.toLowerCase() ).toBe( + html.toLowerCase() + ); + } ); +} ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-gallery.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-gallery.test.js index 5872ad69803d0f..37211a5db2e0bb 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-gallery.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-gallery.test.js @@ -1,51 +1,16 @@ /** * Internal dependencies */ -import EditorPage from './pages/editor-page'; -import { setupDriver, isLocalEnvironment, stopDriver } from './helpers/utils'; - -jest.setTimeout( 1000000 ); +import { blockNames } from './pages/editor-page'; describe( 'Gutenberg Editor Gallery Block tests', () => { - let driver; - let editorPage; - let allPassed = true; - const galleryBlockName = 'Gallery'; - - // Use reporter for setting status for saucelabs Job - if ( ! isLocalEnvironment() ) { - const reporter = { - specDone: async ( result ) => { - allPassed = allPassed && result.status !== 'failed'; - }, - }; - - jasmine.getEnv().addReporter( reporter ); - } - - beforeAll( async () => { - driver = await setupDriver(); - editorPage = new EditorPage( driver ); - } ); - - it( 'should be able to see visual editor', async () => { - await expect( editorPage.getBlockList() ).resolves.toBe( true ); - } ); - it( 'should be able to add a gallery block', async () => { - await editorPage.addNewBlock( galleryBlockName ); + await editorPage.addNewBlock( blockNames.gallery ); const galleryBlock = await editorPage.getBlockAtPosition( - galleryBlockName + blockNames.gallery ); expect( galleryBlock ).toBeTruthy(); - await editorPage.removeBlockAtPosition( galleryBlockName ); - } ); - - afterAll( async () => { - if ( ! isLocalEnvironment() ) { - driver.sauceJobStatus( allPassed ); - } - await stopDriver( driver ); + await editorPage.removeBlockAtPosition( blockNames.gallery ); } ); } ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-heading.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-heading.test.js index 3a40a08df918fa..507ec388652945 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-heading.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-heading.test.js @@ -1,48 +1,15 @@ /** * Internal dependencies */ -import EditorPage from './pages/editor-page'; -import { - setupDriver, - isLocalEnvironment, - stopDriver, - isAndroid, -} from './helpers/utils'; +import { blockNames } from './pages/editor-page'; +import { isAndroid } from './helpers/utils'; import testData from './helpers/test-data'; -jest.setTimeout( 1000000 ); - describe( 'Gutenberg Editor tests @canary', () => { - let driver; - let editorPage; - let allPassed = true; - const paragraphBlockName = 'Paragraph'; - const headingBlockName = 'Heading'; - - // Use reporter for setting status for saucelabs Job - if ( ! isLocalEnvironment() ) { - const reporter = { - specDone: async ( result ) => { - allPassed = allPassed && result.status !== 'failed'; - }, - }; - - jasmine.getEnv().addReporter( reporter ); - } - - beforeAll( async () => { - driver = await setupDriver(); - editorPage = new EditorPage( driver ); - } ); - - it( 'should be able to see visual editor', async () => { - await expect( editorPage.getBlockList() ).resolves.toBe( true ); - } ); - it( 'should be able to create a post with heading and paragraph blocks', async () => { - await editorPage.addNewBlock( headingBlockName ); + await editorPage.addNewBlock( blockNames.heading ); let headingBlockElement = await editorPage.getBlockAtPosition( - headingBlockName + blockNames.heading ); if ( isAndroid() ) { await headingBlockElement.click(); @@ -53,9 +20,9 @@ describe( 'Gutenberg Editor tests @canary', () => { false ); - await editorPage.addNewBlock( paragraphBlockName ); + await editorPage.addNewBlock( blockNames.paragraph ); let paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName, + blockNames.paragraph, 2 ); await editorPage.typeTextToParagraphBlock( @@ -63,9 +30,9 @@ describe( 'Gutenberg Editor tests @canary', () => { testData.mediumText ); - await editorPage.addNewBlock( paragraphBlockName ); + await editorPage.addNewBlock( blockNames.paragraph ); paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName, + blockNames.paragraph, 3 ); await editorPage.typeTextToParagraphBlock( @@ -73,9 +40,9 @@ describe( 'Gutenberg Editor tests @canary', () => { testData.mediumText ); - await editorPage.addNewBlock( headingBlockName ); + await editorPage.addNewBlock( blockNames.heading ); headingBlockElement = await editorPage.getBlockAtPosition( - headingBlockName, + blockNames.heading, 4 ); await editorPage.typeTextToParagraphBlock( @@ -83,9 +50,9 @@ describe( 'Gutenberg Editor tests @canary', () => { testData.heading ); - await editorPage.addNewBlock( paragraphBlockName ); + await editorPage.addNewBlock( blockNames.paragraph ); paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName, + blockNames.paragraph, 5 ); await editorPage.typeTextToParagraphBlock( @@ -93,11 +60,4 @@ describe( 'Gutenberg Editor tests @canary', () => { testData.mediumText ); } ); - - afterAll( async () => { - if ( ! isLocalEnvironment() ) { - driver.sauceJobStatus( allPassed ); - } - await stopDriver( driver ); - } ); } ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-image.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-image.test.js index a96e61a1c133e4..d0f28f6c68249f 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-image.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-image.test.js @@ -1,49 +1,16 @@ /** * Internal dependencies */ -import EditorPage from './pages/editor-page'; -import { - setupDriver, - isLocalEnvironment, - stopDriver, - isAndroid, - clickMiddleOfElement, - swipeUp, -} from './helpers/utils'; +import { blockNames } from './pages/editor-page'; +import { isAndroid, clickMiddleOfElement, swipeUp } from './helpers/utils'; import testData from './helpers/test-data'; -jest.setTimeout( 1000000 ); - describe( 'Gutenberg Editor Image Block tests @canary', () => { - let driver; - let editorPage; - let allPassed = true; - const imageBlockName = 'Image'; - const paragraphBlockName = 'Paragraph'; - - // Use reporter for setting status for saucelabs Job - if ( ! isLocalEnvironment() ) { - const reporter = { - specDone: async ( result ) => { - allPassed = allPassed && result.status !== 'failed'; - }, - }; - - jasmine.getEnv().addReporter( reporter ); - } - - beforeAll( async () => { - driver = await setupDriver(); - editorPage = new EditorPage( driver ); - } ); - - it( 'should be able to see visual editor', async () => { - await expect( editorPage.getBlockList() ).resolves.toBe( true ); - } ); - it( 'should be able to add an image block', async () => { - await editorPage.addNewBlock( imageBlockName ); - let imageBlock = await editorPage.getBlockAtPosition( imageBlockName ); + await editorPage.addNewBlock( blockNames.image ); + let imageBlock = await editorPage.getBlockAtPosition( + blockNames.image + ); // Can only add image from media library on iOS if ( ! isAndroid() ) { @@ -52,21 +19,21 @@ describe( 'Gutenberg Editor Image Block tests @canary', () => { // Workaround because of #952 const titleElement = await editorPage.getTitleElement(); - await clickMiddleOfElement( driver, titleElement ); + await clickMiddleOfElement( editorPage.driver, titleElement ); await editorPage.dismissKeyboard(); // end workaround imageBlock = await editorPage.getBlockAtPosition( imageBlock ); - await swipeUp( driver, imageBlock ); + await swipeUp( editorPage.driver, imageBlock ); await editorPage.enterCaptionToSelectedImageBlock( testData.imageCaption, true ); await editorPage.dismissKeyboard(); } - await editorPage.addNewBlock( paragraphBlockName ); + await editorPage.addNewBlock( blockNames.paragraph ); const paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName, + blockNames.paragraph, 2 ); if ( isAndroid() ) { @@ -77,14 +44,11 @@ describe( 'Gutenberg Editor Image Block tests @canary', () => { // skip HTML check for Android since we couldn't add image from media library if ( ! isAndroid() ) { - await editorPage.verifyHtmlContent( testData.imageShorteHtml ); - } - } ); + const html = await editorPage.getHtmlContent(); - afterAll( async () => { - if ( ! isLocalEnvironment() ) { - driver.sauceJobStatus( allPassed ); + expect( testData.imageShorteHtml.toLowerCase() ).toBe( + html.toLowerCase() + ); } - await stopDriver( driver ); } ); } ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-latest-posts.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-latest-posts.test.js index 160965d1afe967..a5b8ec8d1a9eda 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-latest-posts.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-latest-posts.test.js @@ -1,51 +1,16 @@ /** * Internal dependencies */ -import EditorPage from './pages/editor-page'; -import { setupDriver, isLocalEnvironment, stopDriver } from './helpers/utils'; - -jest.setTimeout( 1000000 ); +import { blockNames } from './pages/editor-page'; describe( 'Gutenberg Editor Latest Post Block tests', () => { - let driver; - let editorPage; - let allPassed = true; - const lastPostBlockName = 'Latest Posts'; - - // Use reporter for setting status for saucelabs Job - if ( ! isLocalEnvironment() ) { - const reporter = { - specDone: async ( result ) => { - allPassed = allPassed && result.status !== 'failed'; - }, - }; - - jasmine.getEnv().addReporter( reporter ); - } - - beforeAll( async () => { - driver = await setupDriver(); - editorPage = new EditorPage( driver ); - } ); - - it( 'should be able to see visual editor', async () => { - await expect( editorPage.getBlockList() ).resolves.toBe( true ); - } ); - it( 'should be able to add a Latests-Posts block', async () => { - await editorPage.addNewBlock( lastPostBlockName ); + await editorPage.addNewBlock( blockNames.latestPosts ); const latestPostsBlock = await editorPage.getBlockAtPosition( - lastPostBlockName + blockNames.latestPosts ); expect( latestPostsBlock ).toBeTruthy(); - await editorPage.removeBlockAtPosition( lastPostBlockName ); - } ); - - afterAll( async () => { - if ( ! isLocalEnvironment() ) { - driver.sauceJobStatus( allPassed ); - } - await stopDriver( driver ); + await editorPage.removeBlockAtPosition( blockNames.latestPosts ); } ); } ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-canary.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-canary.test.js index 09fc2ce7d1d5b3..89d04d0d2cb1cc 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-canary.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-canary.test.js @@ -1,47 +1,15 @@ /** * Internal dependencies */ -import EditorPage from './pages/editor-page'; -import { - isAndroid, - isLocalEnvironment, - setupDriver, - stopDriver, -} from './helpers/utils'; +import { blockNames } from './pages/editor-page'; +import { isAndroid } from './helpers/utils'; import testData from './helpers/test-data'; -jest.setTimeout( 1000000 ); - describe( 'Gutenberg Editor tests for List block @canary', () => { - let driver; - let editorPage; - let allPassed = true; - const listBlockName = 'List'; - - // Use reporter for setting status for saucelabs Job - if ( ! isLocalEnvironment() ) { - const reporter = { - specDone: async ( result ) => { - allPassed = allPassed && result.status !== 'failed'; - }, - }; - - jasmine.getEnv().addReporter( reporter ); - } - - beforeAll( async () => { - driver = await setupDriver(); - editorPage = new EditorPage( driver ); - } ); - - it( 'should be able to see visual editor', async () => { - await expect( editorPage.getBlockList() ).resolves.toBe( true ); - } ); - it( 'should be able to add a new List block', async () => { - await editorPage.addNewBlock( listBlockName ); + await editorPage.addNewBlock( blockNames.list ); const listBlockElement = await editorPage.getBlockAtPosition( - listBlockName + blockNames.list ); // Click List block on Android to force EditText focus if ( isAndroid() ) { @@ -64,13 +32,14 @@ describe( 'Gutenberg Editor tests for List block @canary', () => { ); // switch to html and verify html - await editorPage.verifyHtmlContent( testData.listHtml ); + const html = await editorPage.getHtmlContent(); + expect( testData.listHtml.toLowerCase() ).toBe( html.toLowerCase() ); } ); // This test depends on being run immediately after 'should be able to add a new List block' it( 'should update format to ordered list, using toolbar button', async () => { let listBlockElement = await editorPage.getBlockAtPosition( - listBlockName + blockNames.list ); // Click List block to force EditText focus @@ -80,18 +49,15 @@ describe( 'Gutenberg Editor tests for List block @canary', () => { await editorPage.clickOrderedListToolBarButton(); // switch to html and verify html - await editorPage.verifyHtmlContent( testData.listHtmlOrdered ); - + const html = await editorPage.getHtmlContent(); + expect( testData.listHtmlOrdered.toLowerCase() ).toBe( + html.toLowerCase() + ); // Remove list block to return editor to empty state - listBlockElement = await editorPage.getBlockAtPosition( listBlockName ); + listBlockElement = await editorPage.getBlockAtPosition( + blockNames.list + ); await listBlockElement.click(); - await editorPage.removeBlockAtPosition( listBlockName ); - } ); - - afterAll( async () => { - if ( ! isLocalEnvironment() ) { - driver.sauceJobStatus( allPassed ); - } - await stopDriver( driver ); + await editorPage.removeBlockAtPosition( blockNames.list ); } ); } ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-end.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-end.test.js index a39edd83c9fc29..a08fab00043b4c 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-end.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-lists-end.test.js @@ -1,47 +1,15 @@ /** * Internal dependencies */ -import EditorPage from './pages/editor-page'; -import { - setupDriver, - isLocalEnvironment, - stopDriver, - isAndroid, -} from './helpers/utils'; +import { blockNames } from './pages/editor-page'; +import { isAndroid } from './helpers/utils'; import testData from './helpers/test-data'; -jest.setTimeout( 1000000 ); - describe( 'Gutenberg Editor tests for List block (end)', () => { - let driver; - let editorPage; - let allPassed = true; - const listBlockName = 'List'; - - // Use reporter for setting status for saucelabs Job - if ( ! isLocalEnvironment() ) { - const reporter = { - specDone: async ( result ) => { - allPassed = allPassed && result.status !== 'failed'; - }, - }; - - jasmine.getEnv().addReporter( reporter ); - } - - beforeAll( async () => { - driver = await setupDriver(); - editorPage = new EditorPage( driver ); - } ); - - it( 'should be able to see visual editor', async () => { - await expect( editorPage.getBlockList() ).resolves.toBe( true ); - } ); - it( 'should be able to end a List block', async () => { - await editorPage.addNewBlock( listBlockName ); + await editorPage.addNewBlock( blockNames.list ); const listBlockElement = await editorPage.getBlockAtPosition( - listBlockName + blockNames.list ); // Click List block on Android to force EditText focus @@ -61,13 +29,9 @@ describe( 'Gutenberg Editor tests for List block (end)', () => { // send an Enter await editorPage.sendTextToListBlock( listBlockElement, '\n' ); - await editorPage.verifyHtmlContent( testData.listEndedHtml ); - } ); - - afterAll( async () => { - if ( ! isLocalEnvironment() ) { - driver.sauceJobStatus( allPassed ); - } - await stopDriver( driver ); + const html = await editorPage.getHtmlContent(); + expect( testData.listEndedHtml.toLowerCase() ).toBe( + html.toLowerCase() + ); } ); } ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-lists.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-lists.test.js index 3ce5a3b9fece8a..dbde8dbcfaf6f2 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-lists.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-lists.test.js @@ -1,48 +1,15 @@ /** * Internal dependencies */ -import EditorPage from './pages/editor-page'; -import { - backspace, - isAndroid, - isLocalEnvironment, - setupDriver, - stopDriver, -} from './helpers/utils'; - -jest.setTimeout( 1000000 ); +import { blockNames } from './pages/editor-page'; +import { backspace, isAndroid } from './helpers/utils'; describe( 'Gutenberg Editor tests for List block', () => { - let driver; - let editorPage; - let allPassed = true; - const listBlockName = 'List'; - - // Use reporter for setting status for saucelabs Job - if ( ! isLocalEnvironment() ) { - const reporter = { - specDone: async ( result ) => { - allPassed = allPassed && result.status !== 'failed'; - }, - }; - - jasmine.getEnv().addReporter( reporter ); - } - - beforeAll( async () => { - driver = await setupDriver(); - editorPage = new EditorPage( driver ); - } ); - - it( 'should be able to see visual editor', async () => { - await expect( editorPage.getBlockList() ).resolves.toBe( true ); - } ); - // Prevent regression of https://github.com/wordpress-mobile/gutenberg-mobile/issues/871 it( 'should handle spaces in a list', async () => { - await editorPage.addNewBlock( listBlockName ); + await editorPage.addNewBlock( blockNames.list ); let listBlockElement = await editorPage.getBlockAtPosition( - listBlockName + blockNames.list ); // Click List block on Android to force EditText focus if ( isAndroid() ) { @@ -59,20 +26,18 @@ describe( 'Gutenberg Editor tests for List block', () => { await editorPage.sendTextToListBlock( listBlockElement, backspace ); // switch to html and verify html - await editorPage.verifyHtmlContent( `<!-- wp:list --> + const html = await editorPage.getHtmlContent(); + expect( + `<!-- wp:list --> <ul><li> a</li></ul> -<!-- /wp:list -->` ); +<!-- /wp:list -->` + ).toBe( html.toLowerCase() ); // Remove list block to reset editor to clean state - listBlockElement = await editorPage.getBlockAtPosition( listBlockName ); + listBlockElement = await editorPage.getBlockAtPosition( + blockNames.list + ); await listBlockElement.click(); - await editorPage.removeBlockAtPosition( listBlockName ); - } ); - - afterAll( async () => { - if ( ! isLocalEnvironment() ) { - driver.sauceJobStatus( allPassed ); - } - await stopDriver( driver ); + await editorPage.removeBlockAtPosition( blockNames.list ); } ); } ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-more.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-more.test.js index 7b6aed2b2266c3..115a849df3df61 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-more.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-more.test.js @@ -1,51 +1,16 @@ /** * Internal dependencies */ -import EditorPage from './pages/editor-page'; -import { setupDriver, isLocalEnvironment, stopDriver } from './helpers/utils'; - -jest.setTimeout( 1000000 ); +import { blockNames } from './pages/editor-page'; describe( 'Gutenberg Editor Spacer Block test', () => { - let driver; - let editorPage; - let allPassed = true; - const moreBlockName = 'More'; - - // Use reporter for setting status for saucelabs Job - if ( ! isLocalEnvironment() ) { - const reporter = { - specDone: async ( result ) => { - allPassed = allPassed && result.status !== 'failed'; - }, - }; - - jasmine.getEnv().addReporter( reporter ); - } - - beforeAll( async () => { - driver = await setupDriver(); - editorPage = new EditorPage( driver ); - } ); - - it( 'should be able to see visual editor', async () => { - await expect( editorPage.getBlockList() ).resolves.toBe( true ); - } ); - it( 'should be able to add an separator block', async () => { - await editorPage.addNewBlock( moreBlockName ); + await editorPage.addNewBlock( blockNames.more ); const separatorBlock = await editorPage.getBlockAtPosition( - moreBlockName + blockNames.more ); expect( separatorBlock ).toBeTruthy(); - await editorPage.removeBlockAtPosition( moreBlockName ); - } ); - - afterAll( async () => { - if ( ! isLocalEnvironment() ) { - driver.sauceJobStatus( allPassed ); - } - await stopDriver( driver ); + await editorPage.removeBlockAtPosition( blockNames.more ); } ); } ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-paragraph.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-paragraph.test.js index cd9dbd7a5c61ac..f6ab1e191fc541 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-paragraph.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-paragraph.test.js @@ -1,51 +1,20 @@ /** * Internal dependencies */ -import EditorPage from './pages/editor-page'; +import { blockNames } from './pages/editor-page'; import { backspace, - setupDriver, - isLocalEnvironment, clickMiddleOfElement, clickBeginningOfElement, - stopDriver, - swipeUp, isAndroid, } from './helpers/utils'; import testData from './helpers/test-data'; -jest.setTimeout( 1000000 ); - describe( 'Gutenberg Editor tests for Paragraph Block', () => { - let driver; - let editorPage; - let allPassed = true; - const paragraphBlockName = 'Paragraph'; - - // Use reporter for setting status for saucelabs Job - if ( ! isLocalEnvironment() ) { - const reporter = { - specDone: async ( result ) => { - allPassed = allPassed && result.status !== 'failed'; - }, - }; - - jasmine.getEnv().addReporter( reporter ); - } - - beforeAll( async () => { - driver = await setupDriver(); - editorPage = new EditorPage( driver ); - } ); - - it( 'should be able to see visual editor', async () => { - await expect( editorPage.getBlockList() ).resolves.toBe( true ); - } ); - it( 'should be able to add a new Paragraph block', async () => { - await editorPage.addNewBlock( paragraphBlockName ); + await editorPage.addNewBlock( blockNames.paragraph ); const paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName + blockNames.paragraph ); if ( isAndroid() ) { await paragraphBlockElement.click(); @@ -55,13 +24,13 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { paragraphBlockElement, testData.shortText ); - await editorPage.removeBlockAtPosition( paragraphBlockName ); + await editorPage.removeBlockAtPosition( blockNames.paragraph ); } ); it( 'should be able to split one paragraph block into two', async () => { - await editorPage.addNewBlock( paragraphBlockName ); + await editorPage.addNewBlock( blockNames.paragraph ); const paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName + blockNames.paragraph ); if ( isAndroid() ) { await paragraphBlockElement.click(); @@ -74,15 +43,21 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { const textViewElement = await editorPage.getTextViewForParagraphBlock( paragraphBlockElement ); - await clickMiddleOfElement( driver, textViewElement ); + await clickMiddleOfElement( editorPage.driver, textViewElement ); await editorPage.typeTextToParagraphBlock( paragraphBlockElement, '\n', false ); expect( - ( await editorPage.hasBlockAtPosition( 1, paragraphBlockName ) ) && - ( await editorPage.hasBlockAtPosition( 2, paragraphBlockName ) ) + ( await editorPage.hasBlockAtPosition( + 1, + blockNames.paragraph + ) ) && + ( await editorPage.hasBlockAtPosition( + 2, + blockNames.paragraph + ) ) ).toBe( true ); const text0 = await editorPage.getTextForParagraphBlockAtPosition( 1 ); @@ -93,14 +68,14 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { new RegExp( `${ text0 + text1 }|${ text0 } ${ text1 }` ) ); - await editorPage.removeBlockAtPosition( paragraphBlockName, 2 ); - await editorPage.removeBlockAtPosition( paragraphBlockName ); + await editorPage.removeBlockAtPosition( blockNames.paragraph, 2 ); + await editorPage.removeBlockAtPosition( blockNames.paragraph ); } ); it( 'should be able to merge 2 paragraph blocks into 1', async () => { - await editorPage.addNewBlock( paragraphBlockName ); + await editorPage.addNewBlock( blockNames.paragraph ); let paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName + blockNames.paragraph ); if ( isAndroid() ) { await paragraphBlockElement.click(); @@ -113,20 +88,26 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { let textViewElement = await editorPage.getTextViewForParagraphBlock( paragraphBlockElement ); - await clickMiddleOfElement( driver, textViewElement ); + await clickMiddleOfElement( editorPage.driver, textViewElement ); await editorPage.typeTextToParagraphBlock( paragraphBlockElement, '\n' ); expect( - ( await editorPage.hasBlockAtPosition( 1, paragraphBlockName ) ) && - ( await editorPage.hasBlockAtPosition( 2, paragraphBlockName ) ) + ( await editorPage.hasBlockAtPosition( + 1, + blockNames.paragraph + ) ) && + ( await editorPage.hasBlockAtPosition( + 2, + blockNames.paragraph + ) ) ).toBe( true ); const text0 = await editorPage.getTextForParagraphBlockAtPosition( 1 ); const text1 = await editorPage.getTextForParagraphBlockAtPosition( 2 ); paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName, + blockNames.paragraph, 2 ); if ( isAndroid() ) { @@ -136,7 +117,7 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { textViewElement = await editorPage.getTextViewForParagraphBlock( paragraphBlockElement ); - await clickBeginningOfElement( driver, textViewElement ); + await clickBeginningOfElement( editorPage.driver, textViewElement ); await editorPage.typeTextToParagraphBlock( paragraphBlockElement, backspace @@ -146,15 +127,15 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { expect( text0 + text1 ).toMatch( text ); expect( - await editorPage.hasBlockAtPosition( 2, paragraphBlockName ) + await editorPage.hasBlockAtPosition( 2, blockNames.paragraph ) ).toBe( false ); - await editorPage.removeBlockAtPosition( paragraphBlockName ); + await editorPage.removeBlockAtPosition( blockNames.paragraph ); } ); it( 'should be able to create a post with multiple paragraph blocks', async () => { - await editorPage.addNewBlock( paragraphBlockName ); + await editorPage.addNewBlock( blockNames.paragraph ); const paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName + blockNames.paragraph ); if ( isAndroid() ) { await paragraphBlockElement.click(); @@ -163,8 +144,7 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { await editorPage.sendTextToParagraphBlock( 1, testData.longText ); for ( let i = 3; i > 0; i-- ) { - await swipeUp( driver ); - await editorPage.removeBlockAtPosition( paragraphBlockName, i ); + await editorPage.removeBlockAtPosition( blockNames.paragraph, i ); } } ); @@ -182,11 +162,11 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { // // Merge paragraphs const secondParagraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName, + blockNames.paragraph, 2 ); await clickBeginningOfElement( - driver, + editorPage.driver, secondParagraphBlockElement ); await editorPage.typeTextToParagraphBlock( @@ -200,7 +180,7 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { ); expect( text.length ).not.toEqual( 0 ); - await editorPage.removeBlockAtPosition( paragraphBlockName ); + await editorPage.removeBlockAtPosition( blockNames.paragraph ); } ); // Based on https://github.com/wordpress-mobile/gutenberg-mobile/pull/1507 @@ -216,11 +196,11 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { // // Merge paragraphs const secondParagraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName, + blockNames.paragraph, 2 ); await clickBeginningOfElement( - driver, + editorPage.driver, secondParagraphBlockElement ); await editorPage.typeTextToParagraphBlock( @@ -234,14 +214,7 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { ); expect( text.length ).not.toEqual( 0 ); - await editorPage.removeBlockAtPosition( paragraphBlockName ); + await editorPage.removeBlockAtPosition( blockNames.paragraph ); } ); } - - afterAll( async () => { - if ( ! isLocalEnvironment() ) { - driver.sauceJobStatus( allPassed ); - } - await stopDriver( driver ); - } ); } ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-paste.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-paste.test.js index cd3ace63eb9a1d..95c94efebaefcc 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-paste.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-paste.test.js @@ -1,54 +1,33 @@ /** * Internal dependencies */ -import EditorPage from './pages/editor-page'; +import { blockNames } from './pages/editor-page'; import { - setupDriver, - isLocalEnvironment, longPressMiddleOfElement, tapSelectAllAboveElement, tapCopyAboveElement, tapPasteAboveElement, - stopDriver, isAndroid, } from './helpers/utils'; import testData from './helpers/test-data'; -jest.setTimeout( 1000000 ); - describe( 'Gutenberg Editor paste tests', () => { // skip iOS for now if ( ! isAndroid() ) { - it( 'skips the tests on any platform other than Android', async () => {} ); + it( 'skips the tests on any platform other than Android', async () => { + expect( true ).toBe( true ); + } ); return; } - let driver; - let editorPage; - let allPassed = true; - const paragraphBlockName = 'Paragraph'; - - // Use reporter for setting status for saucelabs Job - if ( ! isLocalEnvironment() ) { - const reporter = { - specDone: async ( result ) => { - allPassed = allPassed && result.status !== 'failed'; - }, - }; - - jasmine.getEnv().addReporter( reporter ); - } - beforeAll( async () => { - driver = await setupDriver(); - await driver.setClipboard( '', 'plaintext' ); - editorPage = new EditorPage( driver ); + await editorPage.driver.setClipboard( '', 'plaintext' ); } ); it( 'copies plain text from one paragraph block and pastes in another', async () => { - await editorPage.addNewBlock( paragraphBlockName ); + await editorPage.addNewBlock( blockNames.paragraph ); const paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName + blockNames.paragraph ); if ( isAndroid() ) { await paragraphBlockElement.click(); @@ -63,14 +42,18 @@ describe( 'Gutenberg Editor paste tests', () => { ); // copy content to clipboard - await longPressMiddleOfElement( driver, textViewElement ); - await tapSelectAllAboveElement( driver, textViewElement ); - await tapCopyAboveElement( driver, textViewElement ); + await longPressMiddleOfElement( editorPage.driver, textViewElement ); + await tapSelectAllAboveElement( editorPage.driver, textViewElement ); + await tapCopyAboveElement( editorPage.driver, textViewElement ); // create another paragraph block - await editorPage.addNewBlock( paragraphBlockName ); + await editorPage.addNewBlock( blockNames.paragraph ); + if ( isAndroid() ) { + // On Andrdoid 10 a new auto-suggestion popup is appearing to let the user paste text recently put in the clipboard. Let's dismiss it. + await editorPage.dismissAndroidClipboardSmartSuggestion(); + } const paragraphBlockElement2 = await editorPage.getBlockAtPosition( - paragraphBlockName, + blockNames.paragraph, 2 ); if ( isAndroid() ) { @@ -82,8 +65,8 @@ describe( 'Gutenberg Editor paste tests', () => { ); // paste into second paragraph block - await longPressMiddleOfElement( driver, textViewElement2 ); - await tapPasteAboveElement( driver, textViewElement2 ); + await longPressMiddleOfElement( editorPage.driver, textViewElement2 ); + await tapPasteAboveElement( editorPage.driver, textViewElement2 ); const text = await editorPage.getTextForParagraphBlockAtPosition( 2 ); expect( text ).toBe( testData.pastePlainText ); @@ -93,7 +76,7 @@ describe( 'Gutenberg Editor paste tests', () => { // create paragraph block with styled text by editing html await editorPage.setHtmlContent( testData.pasteHtmlText ); const paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName + blockNames.paragraph ); if ( isAndroid() ) { await paragraphBlockElement.click(); @@ -104,14 +87,18 @@ describe( 'Gutenberg Editor paste tests', () => { ); // copy content to clipboard - await longPressMiddleOfElement( driver, textViewElement ); - await tapSelectAllAboveElement( driver, textViewElement ); - await tapCopyAboveElement( driver, textViewElement ); + await longPressMiddleOfElement( editorPage.driver, textViewElement ); + await tapSelectAllAboveElement( editorPage.driver, textViewElement ); + await tapCopyAboveElement( editorPage.driver, textViewElement ); // create another paragraph block - await editorPage.addNewBlock( paragraphBlockName ); + await editorPage.addNewBlock( blockNames.paragraph ); + if ( isAndroid() ) { + // On Andrdoid 10 a new auto-suggestion popup is appearing to let the user paste text recently put in the clipboard. Let's dismiss it. + await editorPage.dismissAndroidClipboardSmartSuggestion(); + } const paragraphBlockElement2 = await editorPage.getBlockAtPosition( - paragraphBlockName, + blockNames.paragraph, 2 ); if ( isAndroid() ) { @@ -123,17 +110,13 @@ describe( 'Gutenberg Editor paste tests', () => { ); // paste into second paragraph block - await longPressMiddleOfElement( driver, textViewElement2 ); - await tapPasteAboveElement( driver, textViewElement2 ); + await longPressMiddleOfElement( editorPage.driver, textViewElement2 ); + await tapPasteAboveElement( editorPage.driver, textViewElement2 ); // check styled text by verifying html contents - await editorPage.verifyHtmlContent( testData.pasteHtmlTextResult ); - } ); - - afterAll( async () => { - if ( ! isLocalEnvironment() ) { - driver.sauceJobStatus( allPassed ); - } - await stopDriver( driver ); + const html = await editorPage.getHtmlContent(); + expect( testData.pasteHtmlTextResult.toLowerCase() ).toBe( + html.toLowerCase() + ); } ); } ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-rotation.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-rotation.test.js index 85e426aa5dff3f..8e6c960f299cfc 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-rotation.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-rotation.test.js @@ -1,48 +1,15 @@ /** * Internal dependencies */ -import EditorPage from './pages/editor-page'; -import { - setupDriver, - isLocalEnvironment, - stopDriver, - isAndroid, - toggleOrientation, -} from './helpers/utils'; +import { blockNames } from './pages/editor-page'; +import { isAndroid, toggleOrientation } from './helpers/utils'; import testData from './helpers/test-data'; -jest.setTimeout( 1000000 ); - describe( 'Gutenberg Editor tests', () => { - let driver; - let editorPage; - let allPassed = true; - const paragraphBlockName = 'Paragraph'; - - // Use reporter for setting status for saucelabs Job - if ( ! isLocalEnvironment() ) { - const reporter = { - specDone: async ( result ) => { - allPassed = allPassed && result.status !== 'failed'; - }, - }; - - jasmine.getEnv().addReporter( reporter ); - } - - beforeAll( async () => { - driver = await setupDriver(); - editorPage = new EditorPage( driver ); - } ); - - it( 'should be able to see visual editor', async () => { - await expect( editorPage.getBlockList() ).resolves.toBe( true ); - } ); - it( 'should be able to add blocks , rotate device and continue adding blocks', async () => { - await editorPage.addNewBlock( paragraphBlockName ); + await editorPage.addNewBlock( blockNames.paragraph ); let paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName + blockNames.paragraph ); if ( isAndroid() ) { await paragraphBlockElement.click(); @@ -53,26 +20,26 @@ describe( 'Gutenberg Editor tests', () => { testData.mediumText ); - await toggleOrientation( driver ); + await toggleOrientation( editorPage.driver ); // On Android the keyboard hides the add block button, let's hide it after rotation if ( isAndroid() ) { - await driver.hideDeviceKeyboard(); + await editorPage.driver.hideDeviceKeyboard(); } - await editorPage.addNewBlock( paragraphBlockName ); + await editorPage.addNewBlock( blockNames.paragraph ); if ( isAndroid() ) { - await driver.hideDeviceKeyboard(); + await editorPage.driver.hideDeviceKeyboard(); } paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName, + blockNames.paragraph, 2 ); while ( ! paragraphBlockElement ) { - await driver.hideDeviceKeyboard(); + await editorPage.driver.hideDeviceKeyboard(); paragraphBlockElement = await editorPage.getBlockAtPosition( - paragraphBlockName, + blockNames.paragraph, 2 ); } @@ -80,15 +47,11 @@ describe( 'Gutenberg Editor tests', () => { paragraphBlockElement, testData.mediumText ); - await toggleOrientation( driver ); - - await editorPage.verifyHtmlContent( testData.deviceRotationHtml ); - } ); + await toggleOrientation( editorPage.driver ); - afterAll( async () => { - if ( ! isLocalEnvironment() ) { - driver.sauceJobStatus( allPassed ); - } - await stopDriver( driver ); + const html = await editorPage.getHtmlContent(); + expect( testData.deviceRotationHtml.toLowerCase() ).toBe( + html.toLowerCase() + ); } ); } ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-separator.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-separator.test.js index 438e1a5c1c9c5b..34c0551bcbd464 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-separator.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-separator.test.js @@ -1,51 +1,16 @@ /** * Internal dependencies */ -import EditorPage from './pages/editor-page'; -import { setupDriver, isLocalEnvironment, stopDriver } from './helpers/utils'; - -jest.setTimeout( 1000000 ); +import { blockNames } from './pages/editor-page'; describe( 'Gutenberg Editor Separator Block test', () => { - let driver; - let editorPage; - let allPassed = true; - const separatorBlockName = 'Separator'; - - // Use reporter for setting status for saucelabs Job - if ( ! isLocalEnvironment() ) { - const reporter = { - specDone: async ( result ) => { - allPassed = allPassed && result.status !== 'failed'; - }, - }; - - jasmine.getEnv().addReporter( reporter ); - } - - beforeAll( async () => { - driver = await setupDriver(); - editorPage = new EditorPage( driver ); - } ); - - it( 'should be able to see visual editor', async () => { - await expect( editorPage.getBlockList() ).resolves.toBe( true ); - } ); - it( 'should be able to add an separator block', async () => { - await editorPage.addNewBlock( separatorBlockName ); + await editorPage.addNewBlock( blockNames.separator ); const separatorBlock = await editorPage.getBlockAtPosition( - separatorBlockName + blockNames.separator ); expect( separatorBlock ).toBeTruthy(); - await editorPage.removeBlockAtPosition( separatorBlockName ); - } ); - - afterAll( async () => { - if ( ! isLocalEnvironment() ) { - driver.sauceJobStatus( allPassed ); - } - await stopDriver( driver ); + await editorPage.removeBlockAtPosition( blockNames.separator ); } ); } ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-spacer.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-spacer.test.js index 2aa09cc523bb15..febd3fb8f6d81e 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-spacer.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-spacer.test.js @@ -1,51 +1,16 @@ /** * Internal dependencies */ -import EditorPage from './pages/editor-page'; -import { setupDriver, isLocalEnvironment, stopDriver } from './helpers/utils'; - -jest.setTimeout( 1000000 ); +import { blockNames } from './pages/editor-page'; describe( 'Gutenberg Editor Spacer Block test', () => { - let driver; - let editorPage; - let allPassed = true; - const spacerBlockName = 'Spacer'; - - // Use reporter for setting status for saucelabs Job - if ( ! isLocalEnvironment() ) { - const reporter = { - specDone: async ( result ) => { - allPassed = allPassed && result.status !== 'failed'; - }, - }; - - jasmine.getEnv().addReporter( reporter ); - } - - beforeAll( async () => { - driver = await setupDriver(); - editorPage = new EditorPage( driver ); - } ); - - it( 'should be able to see visual editor', async () => { - await expect( editorPage.getBlockList() ).resolves.toBe( true ); - } ); - it( 'should be able to add an separator block', async () => { - await editorPage.addNewBlock( spacerBlockName ); + await editorPage.addNewBlock( blockNames.spacer ); const separatorBlock = await editorPage.getBlockAtPosition( - spacerBlockName + blockNames.spacer ); expect( separatorBlock ).toBeTruthy(); - await editorPage.removeBlockAtPosition( spacerBlockName ); - } ); - - afterAll( async () => { - if ( ! isLocalEnvironment() ) { - driver.sauceJobStatus( allPassed ); - } - await stopDriver( driver ); + await editorPage.removeBlockAtPosition( blockNames.spacer ); } ); } ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-unsupported-blocks.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-unsupported-blocks.test.js index 3a1dc715cf44a5..6e1be2a13dddb6 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-unsupported-blocks.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-unsupported-blocks.test.js @@ -1,37 +1,9 @@ /** * Internal dependencies */ -import EditorPage from './pages/editor-page'; -import { setupDriver, isLocalEnvironment, stopDriver } from './helpers/utils'; import testData from './helpers/test-data'; -jest.setTimeout( 1000000 ); - describe( 'Gutenberg Editor Unsupported Block Editor Tests', () => { - let driver; - let editorPage; - let allPassed = true; - - // Use reporter for setting status for saucelabs Job - if ( ! isLocalEnvironment() ) { - const reporter = { - specDone: async ( result ) => { - allPassed = allPassed && result.status !== 'failed'; - }, - }; - - jasmine.getEnv().addReporter( reporter ); - } - - beforeAll( async () => { - driver = await setupDriver(); - editorPage = new EditorPage( driver ); - } ); - - it( 'should be able to see visual editor', async () => { - await expect( editorPage.getBlockList() ).resolves.toBe( true ); - } ); - it( 'should be able to open the unsupported block web view editor', async () => { await editorPage.setHtmlContent( testData.unsupportedBlockHtml ); @@ -48,11 +20,4 @@ describe( 'Gutenberg Editor Unsupported Block Editor Tests', () => { editorPage.getUnsupportedBlockWebView() ).resolves.toBeTruthy(); } ); - - afterAll( async () => { - if ( ! isLocalEnvironment() ) { - driver.sauceJobStatus( allPassed ); - } - await stopDriver( driver ); - } ); } ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-verse.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-verse.test.js index bc8412a3ee832f..93068088f84896 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-verse.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-verse.test.js @@ -1,51 +1,16 @@ /** * Internal dependencies */ -import EditorPage from './pages/editor-page'; -import { setupDriver, isLocalEnvironment, stopDriver } from './helpers/utils'; - -jest.setTimeout( 1000000 ); +import { blockNames } from './pages/editor-page'; describe( 'Gutenberg Editor Verse Block Tests', () => { - let driver; - let editorPage; - let allPassed = true; - const verseBlockName = 'Verse'; - - // Use reporter for setting status for saucelabs Job - if ( ! isLocalEnvironment() ) { - const reporter = { - specDone: async ( result ) => { - allPassed = allPassed && result.status !== 'failed'; - }, - }; - - jasmine.getEnv().addReporter( reporter ); - } - - beforeAll( async () => { - driver = await setupDriver(); - editorPage = new EditorPage( driver ); - } ); - - it( 'should be able to see visual editor', async () => { - await expect( editorPage.getBlockList() ).resolves.toBe( true ); - } ); - it( 'should be able to add a verse block', async () => { - await editorPage.addNewBlock( verseBlockName ); + await editorPage.addNewBlock( blockNames.verse ); const verseBlock = await editorPage.getBlockAtPosition( - verseBlockName + blockNames.verse ); expect( verseBlock ).toBeTruthy(); - await editorPage.removeBlockAtPosition( verseBlockName ); - } ); - - afterAll( async () => { - if ( ! isLocalEnvironment() ) { - driver.sauceJobStatus( allPassed ); - } - await stopDriver( driver ); + await editorPage.removeBlockAtPosition( blockNames.verse ); } ); } ); diff --git a/packages/react-native-editor/__device-tests__/helpers/appium-local.js b/packages/react-native-editor/__device-tests__/helpers/appium-local.js index 71a0cd44aea18b..be44a83f83f6ae 100644 --- a/packages/react-native-editor/__device-tests__/helpers/appium-local.js +++ b/packages/react-native-editor/__device-tests__/helpers/appium-local.js @@ -1,10 +1,10 @@ /** * External dependencies */ -import childProcess from 'child_process'; +const childProcess = require( 'child_process' ); // Spawns an appium process -export const start = ( localAppiumPort ) => +const start = ( localAppiumPort ) => new Promise( ( resolve, reject ) => { const appium = childProcess.spawn( 'appium', [ '--port', @@ -40,14 +40,14 @@ export const start = ( localAppiumPort ) => } ); } ); -export const stop = async ( appium ) => { +const stop = async ( appium ) => { if ( ! appium ) { return; } await appium.kill( 'SIGINT' ); }; -export default { +module.exports = { start, stop, }; diff --git a/packages/react-native-editor/__device-tests__/helpers/caps.js b/packages/react-native-editor/__device-tests__/helpers/caps.js index 9b10b52d068ab1..804ef802fb5c48 100644 --- a/packages/react-native-editor/__device-tests__/helpers/caps.js +++ b/packages/react-native-editor/__device-tests__/helpers/caps.js @@ -1,12 +1,10 @@ const ios = { browserName: '', platformName: 'iOS', - platformVersion: '13.4', - deviceName: 'iPhone 11', os: 'iOS', deviceOrientation: 'portrait', automationName: 'XCUITest', - appiumVersion: '1.16.0', // SauceLabs requires appiumVersion to be specified. + appiumVersion: '1.17.1', // Sauce Labs requires appiumVersion to be specified. app: undefined, // will be set later, locally this is relative to root of project processArguments: { args: [ 'uitesting' ], @@ -15,13 +13,12 @@ const ios = { exports.iosLocal = { ...ios, - platformVersion: '13.4', deviceName: 'iPhone 11', }; exports.iosServer = { ...ios, - platformVersion: '13.0', + platformVersion: '13.4', // Supported Sauce Labs platforms can be found here: https://saucelabs.com/rest/v1/info/platforms/appium deviceName: 'iPhone 11 Simulator', }; diff --git a/packages/react-native-editor/__device-tests__/helpers/test-data.js b/packages/react-native-editor/__device-tests__/helpers/test-data.js index ac77087d640138..389928f570cb0d 100644 --- a/packages/react-native-editor/__device-tests__/helpers/test-data.js +++ b/packages/react-native-editor/__device-tests__/helpers/test-data.js @@ -103,3 +103,36 @@ exports.unsupportedBlockHtml = `<!-- wp:audio --> <figure class="wp-block-audio"><audio controls src="https://www2.cs.uic.edu/~i101/SoundFiles/StarWars60.wav"></audio></figure> <!-- /wp:audio --> `; + +exports.columnsWithDifferentUnitsHtml = `<!-- wp:columns --> +<div class="wp-block-columns"><!-- wp:column {"width":"35%"} --> +<div class="wp-block-column" style="flex-basis:35%"></div> +<!-- /wp:column --> + +<!-- wp:column {"width":"55vw"} --> +<div class="wp-block-column" style="flex-basis:55vw"></div> +<!-- /wp:column --> + +<!-- wp:column {"width":"74rem"} --> +<div class="wp-block-column" style="flex-basis:74rem"></div> +<!-- /wp:column --> + +<!-- wp:column {"width":"74em"} --> +<div class="wp-block-column" style="flex-basis:74em"></div> +<!-- /wp:column --> + +<!-- wp:column {"width":"82px"} --> +<div class="wp-block-column" style="flex-basis:82px"></div> +<!-- /wp:column --></div> +<!-- /wp:columns --> +`; + +exports.coverHeightWithRemUnit = `<!-- wp:cover {"customOverlayColor":"#ffffff","minHeight":20,"minHeightUnit":"rem"} --> +<div class="wp-block-cover has-background-dim" style="background-color:#ffffff;min-height:20rem"><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…"} --> +<p class="has-text-align-center"></p> +<!-- /wp:paragraph --></div></div> +<!-- /wp:cover -->`; + +exports.fileBlockPlaceholder = `<!-- wp:file {"id":3,"href":"https://wordpress.org/latest.zip"} --> +<div class="wp-block-file"><a href="https://wordpress.org/latest.zip">WordPress.zip</a><a href="https://wordpress.org/latest.zip" class="wp-block-file__button" download>Download</a></div> +<!-- /wp:file -->`; diff --git a/packages/react-native-editor/__device-tests__/helpers/utils.js b/packages/react-native-editor/__device-tests__/helpers/utils.js index 7f45e747a22ada..b298c390eb0f0d 100644 --- a/packages/react-native-editor/__device-tests__/helpers/utils.js +++ b/packages/react-native-editor/__device-tests__/helpers/utils.js @@ -1,21 +1,19 @@ /** * External dependencies */ -import childProcess from 'child_process'; +const childProcess = require( 'child_process' ); // eslint-disable-next-line import/no-extraneous-dependencies -import wd from 'wd'; -import crypto from 'crypto'; -import path from 'path'; -import fs from 'fs'; - +const wd = require( 'wd' ); +const crypto = require( 'crypto' ); +const path = require( 'path' ); /** * Internal dependencies */ -import serverConfigs from './serverConfigs'; -import { iosServer, iosLocal, android } from './caps'; -import AppiumLocal from './appium-local'; +const serverConfigs = require( './serverConfigs' ); +const { iosServer, iosLocal, android } = require( './caps' ); +const AppiumLocal = require( './appium-local' ); // eslint-disable-next-line import/no-extraneous-dependencies -import _ from 'underscore'; +const _ = require( 'underscore' ); // Platform setup const defaultPlatform = 'android'; @@ -37,8 +35,6 @@ const localIOSAppPath = process.env.IOS_APP_PATH || defaultIOSAppPath; const localAppiumPort = serverConfigs.local.port; // Port to spawn appium process for local runs let appiumProcess; -let iOSScreenRecordingProcess; -let androidScreenRecordingProcess; const backspace = '\u0008'; @@ -59,119 +55,6 @@ const isLocalEnvironment = () => { return testEnvironment.toLowerCase() === 'local'; }; -const isMacOSEnvironment = () => { - return process.platform === 'darwin'; -}; - -const IOS_RECORDINGS_DIR = './ios-screen-recordings'; -const ANDROID_RECORDINGS_DIR = './android-screen-recordings'; - -const getScreenRecordingFileNameBase = ( testPath, id ) => { - const suiteName = path.basename( testPath, '.test.js' ); - return `${ suiteName }.${ id }`; -}; - -jasmine.getEnv().addReporter( { - specStarted: ( { testPath, id } ) => { - if ( ! isMacOSEnvironment() ) { - return; - } - - const fileName = - getScreenRecordingFileNameBase( testPath, id ) + '.mp4'; - - if ( isAndroid() ) { - if ( ! fs.existsSync( ANDROID_RECORDINGS_DIR ) ) { - fs.mkdirSync( ANDROID_RECORDINGS_DIR ); - } - - androidScreenRecordingProcess = childProcess.spawn( 'adb', [ - 'shell', - 'screenrecord', - '--verbose', - '--bit-rate', - '1M', - '--size', - '720x1280', - `/sdcard/${ fileName }`, - ] ); - - androidScreenRecordingProcess.stderr.on( 'data', ( data ) => { - // eslint-disable-next-line no-console - console.log( `Android screen recording error => ${ data }` ); - } ); - - return; - } - - if ( ! fs.existsSync( IOS_RECORDINGS_DIR ) ) { - fs.mkdirSync( IOS_RECORDINGS_DIR ); - } - - iOSScreenRecordingProcess = childProcess.spawn( - 'xcrun', - [ - 'simctl', - 'io', - 'booted', - 'recordVideo', - '--mask=black', - '--force', - fileName, - ], - { - cwd: IOS_RECORDINGS_DIR, - } - ); - }, - specDone: ( { testPath, id, status } ) => { - if ( ! isMacOSEnvironment() ) { - return; - } - - const fileNameBase = getScreenRecordingFileNameBase( testPath, id ); - - if ( isAndroid() ) { - androidScreenRecordingProcess.kill( 'SIGINT' ); - // wait for kill - childProcess.execSync( 'sleep 1' ); - - try { - childProcess.execSync( - `adb pull /sdcard/${ fileNameBase }.mp4 ${ ANDROID_RECORDINGS_DIR }` - ); - } catch ( error ) { - // Some (old) Android devices don't support screen recording or - // sometimes the initial `should be able to see visual editor` - // tests are too fast and a recording is not generated. This is - // when `adb pull` can't find the recording file. In these cases - // we ignore the errors and keep running the tests. - // eslint-disable-next-line no-console - console.log( - `Android screen recording error => ${ error.stdout }` - ); - } - - const oldPath = `${ ANDROID_RECORDINGS_DIR }/${ fileNameBase }.mp4`; - const newPath = `${ ANDROID_RECORDINGS_DIR }/${ fileNameBase }.${ status }.mp4`; - - if ( fs.existsSync( oldPath ) ) { - fs.renameSync( oldPath, newPath ); - } - return; - } - - iOSScreenRecordingProcess.kill( 'SIGINT' ); - - const oldPath = `${ IOS_RECORDINGS_DIR }/${ fileNameBase }.mp4`; - const newPath = `${ IOS_RECORDINGS_DIR }/${ fileNameBase }.${ status }.mp4`; - - if ( fs.existsSync( oldPath ) ) { - fs.renameSync( oldPath, newPath ); - } - }, -} ); - // Initialises the driver and desired capabilities for appium const setupDriver = async () => { const branch = process.env.CIRCLE_BRANCH || ''; diff --git a/packages/react-native-editor/__device-tests__/pages/editor-page.js b/packages/react-native-editor/__device-tests__/pages/editor-page.js index 4673b0b3f2884e..2b08b695935b6a 100644 --- a/packages/react-native-editor/__device-tests__/pages/editor-page.js +++ b/packages/react-native-editor/__device-tests__/pages/editor-page.js @@ -1,16 +1,23 @@ /** * Internal dependencies */ -import { +const { + setupDriver, + stopDriver, isAndroid, swipeUp, swipeDown, typeString, toggleHtmlMode, swipeFromTo, -} from '../helpers/utils'; +} = require( '../helpers/utils' ); -export default class EditorPage { +const initializeEditorPage = async () => { + const driver = await setupDriver(); + return new EditorPage( driver ); +}; + +class EditorPage { driver; accessibilityIdKey; accessibilityIdXPathAttrib; @@ -130,16 +137,16 @@ export default class EditorPage { return await this.driver.elementByXPath( blockLocator ); } - // Converts to lower case and checks for a match to lowercased html content + // Returns html content // Ensure to take additional steps to handle text being changed by auto correct - async verifyHtmlContent( html ) { + async getHtmlContent() { await toggleHtmlMode( this.driver, true ); const htmlContentView = await this.getTextViewForHtmlViewContent(); const text = await htmlContentView.text(); - expect( text.toLowerCase() ).toBe( html.toLowerCase() ); await toggleHtmlMode( this.driver, false ); + return text; } // set html editor content explicitly @@ -167,6 +174,20 @@ export default class EditorPage { await hideKeyboardToolbarButton.click(); } + async dismissAndroidClipboardSmartSuggestion() { + if ( ! isAndroid() ) { + return; + } + + const dismissClipboardSmartSuggestionLocator = `//*[@${ this.accessibilityIdXPathAttrib }="Dismiss Smart Suggestion"]`; + const smartSuggestions = await this.driver.elementsByXPath( + dismissClipboardSmartSuggestionLocator + ); + if ( smartSuggestions.length !== 0 ) { + smartSuggestions[ 0 ].click(); + } + } + // ========================= // Block toolbar functions // ========================= @@ -522,4 +543,30 @@ export default class EditorPage { this.driver.setImplicitWaitTimeout( 5000 ); return element; } + + async stopDriver() { + await stopDriver( this.driver ); + } + + async sauceJobStatus( allPassed ) { + await this.driver.sauceJobStatus( allPassed ); + } } + +const blockNames = { + paragraph: 'Paragraph', + gallery: 'Gallery', + columns: 'Columns', + cover: 'Cover', + heading: 'Heading', + image: 'Image', + latestPosts: 'Latest Posts', + list: 'List', + more: 'More', + separator: 'Separator', + spacer: 'Spacer', + verse: 'Verse', + file: 'File', +}; + +module.exports = { initializeEditorPage, blockNames }; diff --git a/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java index 66a4bfc431b872..628feb8b8b6dbb 100644 --- a/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java +++ b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java @@ -5,6 +5,7 @@ import android.content.res.Configuration; import android.os.Bundle; import android.util.Log; +import android.widget.Toast; import androidx.core.util.Consumer; @@ -36,6 +37,7 @@ import com.swmansion.rnscreens.RNScreensPackage; import com.th3rdwave.safeareacontext.SafeAreaContextPackage; import org.reactnative.maskedview.RNCMaskedViewPackage; +import org.wordpress.mobile.WPAndroidGlue.MediaOption; import java.util.Arrays; import java.util.List; @@ -70,10 +72,18 @@ public void requestMediaPickFromDeviceLibrary(MediaSelectedCallback mediaSelecte @Override public void requestMediaPickFromMediaLibrary(MediaSelectedCallback mediaSelectedCallback, Boolean allowMultipleSelection, MediaType mediaType) { List<RNMedia> rnMediaList = new ArrayList<>(); - if (mediaType == MediaType.IMAGE) { - rnMediaList.add(new Media(1, "https://cldup.com/cXyG__fTLN.jpg", "image", "Mountain" )); - } else if (mediaType == MediaType.VIDEO) { - rnMediaList.add(new Media(2, "https://i.cloudup.com/YtZFJbuQCE.mov", "video", "Cloudup" )); + + switch (mediaType) { + case IMAGE: + rnMediaList.add(new Media(1, "https://cldup.com/cXyG__fTLN.jpg", "image", "Mountain", "")); + break; + case VIDEO: + rnMediaList.add(new Media(2, "https://i.cloudup.com/YtZFJbuQCE.mov", "video", "Cloudup", "")); + case ANY: + case OTHER: + rnMediaList.add(new Media(3, "https://wordpress.org/latest.zip", "zip", "WordPress latest version", "WordPress.zip")); + break; + } mediaSelectedCallback.onMediaFileSelected(rnMediaList); } @@ -83,6 +93,10 @@ public void requestMediaPickFromMediaLibrary(MediaSelectedCallback mediaSelected public void mediaUploadSync(MediaSelectedCallback mediaSelectedCallback) { } + @Override + public void mediaSaveSync(MediaSelectedCallback mediaSelectedCallback) { + } + @Override public void requestImageFailedRetryDialog(int mediaId) { } @@ -105,12 +119,20 @@ public void editorDidAutosave() { @Override public void getOtherMediaPickerOptions(OtherMediaOptionsReceivedCallback otherMediaOptionsReceivedCallback, MediaType mediaType) { - + if (mediaType == MediaType.ANY) { + ArrayList<MediaOption> mediaOptions = new ArrayList<>(); + mediaOptions.add(new MediaOption("1", "Choose from device")); + otherMediaOptionsReceivedCallback.onOtherMediaOptionsReceived(mediaOptions); + } } @Override public void requestMediaPickFrom(String mediaSource, MediaSelectedCallback mediaSelectedCallback, Boolean allowMultipleSelection) { - + if (mediaSource.equals("1")) { + List<RNMedia> rnMediaList = new ArrayList<>(); + rnMediaList.add(new Media(1, "https://grad.illinois.edu/sites/default/files/pdfs/cvsamples.pdf", "other", "","cvsamples.pdf")); + mediaSelectedCallback.onMediaFileSelected(rnMediaList); + } } @Override @@ -171,11 +193,35 @@ public void onAddMention(Consumer<String> onSuccess) { onSuccess.accept("matt"); } + @Override + public void requestMediaFilesEditorLoad( + ReplaceMediaFilesEditedBlockCallback replaceMediaFilesEditedBlockCallback, + ReadableArray mediaFiles, + String blockId + ) { + Toast.makeText(MainApplication.this, "requestMediaFilesEditorLoad called", Toast.LENGTH_SHORT).show(); + } + + @Override + public void requestMediaFilesFailedRetryDialog(ReadableArray mediaFiles) { + Toast.makeText(MainApplication.this, "requestMediaFilesFailedRetryDialog called", Toast.LENGTH_SHORT).show(); + } + + @Override + public void requestMediaFilesUploadCancelDialog(ReadableArray mediaFiles) { + Toast.makeText(MainApplication.this, "requestMediaFilesUploadCancelDialog called", Toast.LENGTH_SHORT).show(); + } + + @Override + public void requestMediaFilesSaveCancelDialog(ReadableArray mediaFiles) { + Toast.makeText(MainApplication.this, "requestMediaFilesSaveCancelDialog called", Toast.LENGTH_SHORT).show(); + } + @Override public void gutenbergDidSendButtonPressedAction(String buttonType) { } - + }, isDarkMode()); return new ReactNativeHost(this) { diff --git a/packages/react-native-editor/ios/DocumentsMediaSource.swift b/packages/react-native-editor/ios/DocumentsMediaSource.swift new file mode 100644 index 00000000000000..5914cf65e0134b --- /dev/null +++ b/packages/react-native-editor/ios/DocumentsMediaSource.swift @@ -0,0 +1,74 @@ +import UIKit +import Gutenberg +import MobileCoreServices + +class DocumentsMediaSource: NSObject { + private var mediaPickerCallback: MediaPickerDidPickMediaCallback? + private unowned var gutenberg: Gutenberg + private unowned var uploadCoordinator: MediaUploadCoordinator + + init(gutenberg: Gutenberg, coordinator: MediaUploadCoordinator) { + self.gutenberg = gutenberg + self.uploadCoordinator = coordinator + } + + func presentPicker(origin: UIViewController, filters: [Gutenberg.MediaType], multipleSelection: Bool, callback: @escaping MediaPickerDidPickMediaCallback) { + let uttypeFilters = filters.compactMap { $0.typeIdentifier } + mediaPickerCallback = callback + let docPicker = UIDocumentPickerViewController(documentTypes: uttypeFilters, in: .import) + docPicker.delegate = self + docPicker.allowsMultipleSelection = multipleSelection + origin.present(docPicker, animated: true) + } +} + + + +extension DocumentsMediaSource: UIDocumentPickerDelegate { + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + defer { + mediaPickerCallback = nil + } + if urls.count == 0 { + mediaPickerCallback?(nil) + } else { + insertOnBlock(with: urls) + } + } + + func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { + mediaPickerCallback?(nil) + mediaPickerCallback = nil + } + + func insertOnBlock(with urls: [URL]) { + guard let callback = mediaPickerCallback else { + return assertionFailure("Image picked without callback") + } + + let mediaInfo = urls.compactMap({ (url) -> MediaInfo? in + let title = url.lastPathComponent + let id = self.uploadCoordinator.upload(url: url) + return MediaInfo(id: id, url: url.absoluteString, type: "file", title: title) + }) + + callback(mediaInfo) + } +} + +extension Gutenberg.MediaType { + var typeIdentifier: String? { + switch self { + case .image: + return String(kUTTypeImage) + case .video: + return String(kUTTypeMovie) + case .audio: + return String(kUTTypeAudio) + case .any: + return String(kUTTypeItem) + case .other: + return nil + } + } +} diff --git a/packages/react-native-editor/ios/GutenbergDemo.xcodeproj/project.pbxproj b/packages/react-native-editor/ios/GutenbergDemo.xcodeproj/project.pbxproj index 10b038afc34a6d..4fd2d0952c0d2e 100644 --- a/packages/react-native-editor/ios/GutenbergDemo.xcodeproj/project.pbxproj +++ b/packages/react-native-editor/ios/GutenbergDemo.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 1E4F2E752459E6F200EB73E7 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E85944D2449D85A006CC6A0 /* WebViewController.swift */; }; + 1EFFAB71253EF6580062051E /* DocumentsMediaSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFFAB70253EF6580062051E /* DocumentsMediaSource.swift */; }; 6EBC6CA237E4D4B00D5AC79F /* Pods_GutenbergDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB79EC55FB340834C8D3BAB6 /* Pods_GutenbergDemo.framework */; }; 7EC7328F21907E3F00FED2E6 /* GutenbergViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EC7328E21907E3F00FED2E6 /* GutenbergViewController.swift */; }; 950B7A8B00E7FAB67173E517 /* Pods_GutenbergDemoTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D550F9EC8C63C5F836281628 /* Pods_GutenbergDemoTests.framework */; }; @@ -46,6 +47,7 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = GutenbergDemo/Images.xcassets; sourceTree = "<group>"; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = GutenbergDemo/Info.plist; sourceTree = "<group>"; }; 1E85944D2449D85A006CC6A0 /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WebViewController.swift; path = GutenbergDemo/WebViewController.swift; sourceTree = "<group>"; }; + 1EFFAB70253EF6580062051E /* DocumentsMediaSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentsMediaSource.swift; sourceTree = "<group>"; }; 66F6B74F51BD6921D3AF25F6 /* Pods-GutenbergDemoTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GutenbergDemoTests.debug.xcconfig"; path = "Target Support Files/Pods-GutenbergDemoTests/Pods-GutenbergDemoTests.debug.xcconfig"; sourceTree = "<group>"; }; 71AC74DFA49CB3BF62D440DB /* Pods-GutenbergDemoTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GutenbergDemoTests.release.xcconfig"; path = "Target Support Files/Pods-GutenbergDemoTests/Pods-GutenbergDemoTests.release.xcconfig"; sourceTree = "<group>"; }; 7EA30CF021AC8CDA0092F894 /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = usr/lib/libxml2.tbd; sourceTree = SDKROOT; }; @@ -114,6 +116,7 @@ F15198372100DC3C000F6E97 /* gutenberg-Bridging-Header.h */, F15198392100DC3D000F6E97 /* AppDelegate.swift */, 7EC7328E21907E3F00FED2E6 /* GutenbergViewController.swift */, + 1EFFAB70253EF6580062051E /* DocumentsMediaSource.swift */, 1E85944D2449D85A006CC6A0 /* WebViewController.swift */, FF9A6F1621FA8E2500D36D14 /* MediaPickCoordinator.swift */, FF6836C722035EAB00A0C562 /* MediaUploadCoordinator.swift */, @@ -469,6 +472,7 @@ FF83DAA92226905A00A34C93 /* CustomImageLoader.swift in Sources */, FF9A6F4121FA8E2500D36D14 /* MediaPickCoordinator.swift in Sources */, F151983D2100DC3D000F6E97 /* MediaProvider.swift in Sources */, + 1EFFAB71253EF6580062051E /* DocumentsMediaSource.swift in Sources */, 1E4F2E752459E6F200EB73E7 /* WebViewController.swift in Sources */, FF6836C822035EAB00A0C562 /* MediaUploadCoordinator.swift in Sources */, 7EC7328F21907E3F00FED2E6 /* GutenbergViewController.swift in Sources */, diff --git a/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift b/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift index 99f9051f0b101d..06ba6a6704bb32 100644 --- a/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift +++ b/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift @@ -1,9 +1,11 @@ - import UIKit import Gutenberg import Aztec class GutenbergViewController: UIViewController { + private lazy var filesAppMediaPicker: DocumentsMediaSource = { + return DocumentsMediaSource(gutenberg: gutenberg, coordinator: mediaUploadCoordinator) + }() fileprivate lazy var gutenberg = Gutenberg(dataSource: self, extraModules: [CustomImageLoader()]) fileprivate var htmlMode = false @@ -50,7 +52,6 @@ class GutenbergViewController: UIViewController { } extension GutenbergViewController: GutenbergBridgeDelegate { - func gutenbergDidRequestFetch(path: String, completion: @escaping (Result<Any, NSError>) -> Void) { completion(Result.success([:])) } @@ -99,6 +100,8 @@ extension GutenbergViewController: GutenbergBridgeDelegate { } else { callback([MediaInfo(id: 2, url: "https://i.cloudup.com/YtZFJbuQCE.mov", type: "video", caption: "Cloudup")]) } + case .other, .any: + callback([MediaInfo(id: 3, url: "https://wordpress.org/latest.zip", type: "zip", caption: "WordPress latest version", title: "WordPress.zip")]) default: break } @@ -108,6 +111,9 @@ extension GutenbergViewController: GutenbergBridgeDelegate { case .deviceCamera: print("Gutenberg did request a device media picker, opening the camera picker") pickAndUpload(from: .camera, filter: currentFilter, callback: callback) + + case .filesApp, .otherApps: + pickAndUploadFromFilesApp(filter: currentFilter, callback: callback) default: break } } @@ -129,6 +135,10 @@ extension GutenbergViewController: GutenbergBridgeDelegate { mediaPickCoordinator?.pick(from: source) } + private func pickAndUploadFromFilesApp(filter: Gutenberg.MediaType, callback: @escaping MediaPickerDidPickMediaCallback) { + filesAppMediaPicker.presentPicker(origin: self, filters: [filter], multipleSelection: false, callback: callback) + } + func gutenbergDidRequestMediaUploadSync() { print("Gutenberg request for media uploads to be resync") } @@ -222,6 +232,26 @@ extension GutenbergViewController: GutenbergBridgeDelegate { func gutenbergDidRequestSetStarterPageTemplatesTooltipShown(_ tooltipShown: Bool) { print("Gutenberg requested setting tooltip flag") } + + func gutenbergDidRequestMediaSaveSync() { + print(#function) + } + + func gutenbergDidRequestMediaFilesEditorLoad(_ mediaFiles: [String], blockId: String) { + print(#function) + } + + func gutenbergDidRequestMediaFilesFailedRetryDialog(_ mediaFiles: [String]) { + print(#function) + } + + func gutenbergDidRequestMediaFilesUploadCancelDialog(_ mediaFiles: [String]) { + print(#function) + } + + func gutenbergDidRequestMediaFilesSaveCancelDialog(_ mediaFiles: [String]) { + print(#function) + } } extension GutenbergViewController: GutenbergWebDelegate { @@ -270,6 +300,7 @@ extension GutenbergViewController: GutenbergBridgeDataSource { .mentions: true, .unsupportedBlockEditor: unsupportedBlockEnabled, .canEnableUnsupportedBlockEditor: unsupportedBlockCanBeActivated, + .mediaFilesCollectionBlock: true, ] } @@ -280,6 +311,15 @@ extension GutenbergViewController: GutenbergBridgeDataSource { func gutenbergEditorTheme() -> GutenbergEditorTheme? { return nil } + + func gutenbergMediaSources() -> [Gutenberg.MediaSource] { + return [.filesApp, .otherApps] + } +} + +extension Gutenberg.MediaSource { + static let filesApp = Gutenberg.MediaSource(id: "files-app", label: "Choose from device", types: [.any]) + static let otherApps = Gutenberg.MediaSource(id: "other-apps", label: "Other Apps", types: [.image, .video, .audio, .other]) } //MARK: - Navigation bar diff --git a/packages/react-native-editor/ios/GutenbergDemo/MediaUploadCoordinator.swift b/packages/react-native-editor/ios/GutenbergDemo/MediaUploadCoordinator.swift index 9571d532d92c87..2655af936b466b 100644 --- a/packages/react-native-editor/ios/GutenbergDemo/MediaUploadCoordinator.swift +++ b/packages/react-native-editor/ios/GutenbergDemo/MediaUploadCoordinator.swift @@ -20,6 +20,7 @@ class MediaUploadCoordinator: NSObject { func upload(url: URL) -> Int32? { //Make sure the media is not larger than a 32 bits to number to avoid problems when bridging to JS + successfullUpload = true let mediaID = Int32(truncatingIfNeeded:UUID().uuidString.hash) let progress = Progress(parent: nil, userInfo: [ProgressUserInfoKey.mediaID: mediaID, ProgressUserInfoKey.mediaURL: url]) progress.totalUnitCount = 100 diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock index 724c271526e5df..39b82482fe5dc7 100644 --- a/packages/react-native-editor/ios/Podfile.lock +++ b/packages/react-native-editor/ios/Podfile.lock @@ -21,8 +21,8 @@ PODS: - DoubleConversion - glog - glog (0.3.5) - - Gutenberg (1.39.0): - - React (= 0.61.5) + - Gutenberg (1.42.1): + - React-Core (= 0.61.5) - React-CoreModules (= 0.61.5) - React-RCTImage (= 0.61.5) - RNTAztecView @@ -252,8 +252,8 @@ PODS: - RNScreens (2.9.0): - React-Core - RNSVG (9.13.6-gb): - - React - - RNTAztecView (1.39.0): + - React-Core + - RNTAztecView (1.42.1): - React-Core - WordPress-Aztec-iOS (~> 1.19.3) - WordPress-Aztec-iOS (1.19.3) @@ -402,7 +402,7 @@ SPEC CHECKSUMS: FBReactNativeSpec: 118d0d177724c2d67f08a59136eb29ef5943ec75 Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51 glog: 1f3da668190260b06b429bb211bfbee5cd790c28 - Gutenberg: 0e8bbcdfaf70218ef497f1508c49866e56a4f25d + Gutenberg: d4257899f1def887029385c03eeadb20f8769506 RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1 RCTTypeSafety: 9aa1b91d7f9310fc6eadc3cf95126ffe818af320 React: b6a59ef847b2b40bb6e0180a97d0ca716969ac78 @@ -412,13 +412,13 @@ SPEC CHECKSUMS: React-jsi: cb2cd74d7ccf4cffb071a46833613edc79cdf8f7 React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386 React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0 - react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c - react-native-get-random-values: 8940331a943a46c165d3ed05802c09c392f8dd46 - react-native-keyboard-aware-scroll-view: ffa9152671fec9a571197ed2d02e0fcb90206e60 - react-native-safe-area: e8230b0017d76c00de6b01e2412dcf86b127c6a3 - react-native-safe-area-context: 344b969c45af3d8464d36e8dea264942992ef033 - react-native-slider: ecb7f25c14f2348d1c1f629a6f2be7611d22a066 - react-native-video: d01ed7ff1e38fa7dcc6c15c94cf505e661b7bfd0 + react-native-blur: ef741a08d020010ba65e411be0ab82d1d325e7ad + react-native-get-random-values: 03edb8dcc2d3f43e55aa67ea13b61b6723bbf047 + react-native-keyboard-aware-scroll-view: 6cb84879bf07e4cc1caed18b11fb928e142adac6 + react-native-safe-area: c9cf765aa2dd96159476a99633e7d462ce5bb94f + react-native-safe-area-context: c8da723dcddabe15afe95c9d31c56a0cf2b0141c + react-native-slider: 2e42dc91e7ab8b35a9c7f2eb3532729a41d0dbe2 + react-native-video: b8767f54061e475ddd38c22375f46f4d93e5fdfd React-RCTActionSheet: 600b4d10e3aea0913b5a92256d2719c0cdd26d76 React-RCTAnimation: 791a87558389c80908ed06cc5dfc5e7920dfa360 React-RCTBlob: d89293cc0236d9cb0933d85e430b0bbe81ad1d72 @@ -429,13 +429,13 @@ SPEC CHECKSUMS: React-RCTText: 9ccc88273e9a3aacff5094d2175a605efa854dbe React-RCTVibration: a49a1f42bf8f5acf1c3e297097517c6b3af377ad ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd - ReactNativeDarkMode: f61376360c5d983907e5c316e8e1c853a8c2f348 - RNCMaskedView: 884452b2a5d593e4c55058b3fedf39c2a34d0743 - RNGestureHandler: dde546180bf24af0b5f737c8ad04b6f3fa51609a - RNReanimated: b5ccb50650ba06f6e749c7c329a1bc3ae0c88b43 - RNScreens: c526239bbe0e957b988dacc8d75ac94ec9cb19da - RNSVG: 68a534a5db06dcbdaebfd5079349191598caef7b - RNTAztecView: e13f4f962634980c42dae7d1fc18b17e2fdd9a9f + ReactNativeDarkMode: 6d807bc8373b872472c8541fc3341817d979a6fb + RNCMaskedView: 66caacf33c86eaa7d22b43178dd998257d5c2e4d + RNGestureHandler: 7a377d0580c9f07df99e590bbcefd616ee0e2626 + RNReanimated: f05baf4cd76b6eab2e4d7e2b244424960b968918 + RNScreens: 953633729a42e23ad0c93574d676b361e3335e8b + RNSVG: 46c4b680fe18237fa01eb7d7b311d77618fde31f + RNTAztecView: 38c4acd97d152a125eba9318125c3a75ea99e1db WordPress-Aztec-iOS: b7ac8b30f746992e85d9668453ac87c2cdcecf4f Yoga: f2a7cd4280bfe2cca5a7aed98ba0eb3d1310f18b diff --git a/test/native/jest_ui.config.js b/packages/react-native-editor/jest_ui.config.js similarity index 79% rename from test/native/jest_ui.config.js rename to packages/react-native-editor/jest_ui.config.js index 15855a1a261e0b..fbbb9f7ff89208 100644 --- a/test/native/jest_ui.config.js +++ b/packages/react-native-editor/jest_ui.config.js @@ -10,7 +10,7 @@ if ( process.env.TEST_RN_PLATFORM ) { module.exports = { verbose: true, - rootDir: '../../', + rootDir: './', haste: { defaultPlatform: rnPlatform, platforms: [ 'android', 'ios', 'native' ], @@ -19,6 +19,8 @@ module.exports = { '^.+\\.(js|ts|tsx)$': 'babel-jest', }, timers: 'real', - setupFiles: [], + setupFilesAfterEnv: [ './jest_ui_setup_after_env.js' ], + testEnvironment: './jest_ui_test_environment.js', testMatch: [ '**/__device-tests__/**/*.test.[jt]s?(x)' ], + reporters: [ 'default', 'jest-junit' ], }; diff --git a/packages/react-native-editor/jest_ui_setup_after_env.js b/packages/react-native-editor/jest_ui_setup_after_env.js new file mode 100644 index 00000000000000..3f90091bf77552 --- /dev/null +++ b/packages/react-native-editor/jest_ui_setup_after_env.js @@ -0,0 +1,146 @@ +/** + * External dependencies + */ +const fs = require( 'fs' ); +const path = require( 'path' ); +const childProcess = require( 'child_process' ); + +/** + * Internal dependencies + */ +const { + isAndroid, + isLocalEnvironment, +} = require( './__device-tests__/helpers/utils' ); + +jest.setTimeout( 1000000 ); // in milliseconds + +let iOSScreenRecordingProcess; +let androidScreenRecordingProcess; + +const isMacOSEnvironment = () => { + return process.platform === 'darwin'; +}; + +const IOS_RECORDINGS_DIR = './ios-screen-recordings'; +const ANDROID_RECORDINGS_DIR = './android-screen-recordings'; + +const getScreenRecordingFileNameBase = ( testPath, id ) => { + const suiteName = path.basename( testPath, '.test.js' ); + return `${ suiteName }.${ id }`; +}; + +let allPassed = true; + +jasmine.getEnv().addReporter( { + specStarted: ( { testPath, id } ) => { + if ( ! isMacOSEnvironment() ) { + return; + } + + const fileName = + getScreenRecordingFileNameBase( testPath, id ) + '.mp4'; + + if ( isAndroid() ) { + if ( ! fs.existsSync( ANDROID_RECORDINGS_DIR ) ) { + fs.mkdirSync( ANDROID_RECORDINGS_DIR ); + } + + androidScreenRecordingProcess = childProcess.spawn( 'adb', [ + 'shell', + 'screenrecord', + '--verbose', + '--bit-rate', + '1M', + '--size', + '720x1280', + `/sdcard/${ fileName }`, + ] ); + + androidScreenRecordingProcess.stderr.on( 'data', ( data ) => { + // eslint-disable-next-line no-console + console.log( `Android screen recording error => ${ data }` ); + } ); + + return; + } + + if ( ! fs.existsSync( IOS_RECORDINGS_DIR ) ) { + fs.mkdirSync( IOS_RECORDINGS_DIR ); + } + + iOSScreenRecordingProcess = childProcess.spawn( + 'xcrun', + [ + 'simctl', + 'io', + 'booted', + 'recordVideo', + '--mask=black', + '--force', + fileName, + ], + { + cwd: IOS_RECORDINGS_DIR, + } + ); + }, + specDone: ( { testPath, id, status } ) => { + allPassed = allPassed && status !== 'failed'; + + if ( ! isMacOSEnvironment() ) { + return; + } + + const fileNameBase = getScreenRecordingFileNameBase( testPath, id ); + + if ( isAndroid() ) { + androidScreenRecordingProcess.kill( 'SIGINT' ); + // wait for kill + childProcess.execSync( 'sleep 1' ); + + try { + childProcess.execSync( + `adb pull /sdcard/${ fileNameBase }.mp4 ${ ANDROID_RECORDINGS_DIR }` + ); + } catch ( error ) { + // Some (old) Android devices don't support screen recording or + // sometimes the initial `should be able to see visual editor` + // tests are too fast and a recording is not generated. This is + // when `adb pull` can't find the recording file. In these cases + // we ignore the errors and keep running the tests. + // eslint-disable-next-line no-console + console.log( + `Android screen recording error => ${ error.stdout }` + ); + } + + const oldPath = `${ ANDROID_RECORDINGS_DIR }/${ fileNameBase }.mp4`; + const newPath = `${ ANDROID_RECORDINGS_DIR }/${ fileNameBase }.${ status }.mp4`; + + if ( fs.existsSync( oldPath ) ) { + fs.renameSync( oldPath, newPath ); + } + return; + } + + iOSScreenRecordingProcess.kill( 'SIGINT' ); + + const oldPath = `${ IOS_RECORDINGS_DIR }/${ fileNameBase }.mp4`; + const newPath = `${ IOS_RECORDINGS_DIR }/${ fileNameBase }.${ status }.mp4`; + + if ( fs.existsSync( oldPath ) ) { + fs.renameSync( oldPath, newPath ); + } + }, + suiteDone() { + if ( ! isLocalEnvironment() ) { + global.editorPage.sauceJobStatus( allPassed ); + } + }, +} ); + +it( 'should be able to see visual editor', async () => { + // wait for the block editor to load + await expect( global.editorPage.getBlockList() ).resolves.toBe( true ); +} ); diff --git a/packages/react-native-editor/jest_ui_test_environment.js b/packages/react-native-editor/jest_ui_test_environment.js new file mode 100644 index 00000000000000..052aa95da0781f --- /dev/null +++ b/packages/react-native-editor/jest_ui_test_environment.js @@ -0,0 +1,26 @@ +/** + * Internal dependencies + */ +const { + initializeEditorPage, +} = require( './__device-tests__/pages/editor-page' ); + +/** + * External dependencies + */ +// eslint-disable-next-line import/no-extraneous-dependencies +const JSDOMEnvironment = require( 'jest-environment-jsdom' ); + +class CustomEnvironment extends JSDOMEnvironment { + async setup() { + await super.setup(); + this.global.editorPage = await initializeEditorPage(); + } + + async teardown() { + await this.global.editorPage.stopDriver(); + await super.teardown(); + } +} + +module.exports = CustomEnvironment; diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index 40086f51b9aac1..a6c0fc85810aaa 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-editor", - "version": "1.40.4", + "version": "1.42.1", "description": "Mobile WordPress gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,7 +29,7 @@ "main": "src/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@react-native-community/blur": "3.6.0", "@react-native-community/masked-view": "git+https://github.com/wordpress-mobile/react-native-masked-view.git#f65a51a3320e58404d7f38d967bfd1f42b439ca9", "@react-native-community/slider": "git+https://github.com/wordpress-mobile/react-native-slider.git#d263ff16cdd9fb7352b354342522ff030f220f42", @@ -86,7 +86,7 @@ "postrn-bundle": "cd ../.. && patch-package --reverse --patch-dir packages/react-native-editor/metro-patch", "prebundle": "npm run i18n-cache:force", "bundle": "npm run bundle:android && npm run bundle:ios", - "bundle:android": "mkdir -p bundle/android && npm run rn-bundle -- --platform android --dev false --entry-file index.js --assets-dest bundle/android --bundle-output bundle/android/App.js --sourcemap-output bundle/android/App.js.map", + "bundle:android": "mkdir -p bundle/android && npm run rn-bundle -- --platform android --dev false --entry-file index.js --assets-dest bundle/android --bundle-output bundle/android/App.text.js --sourcemap-output bundle/android/App.text.js.map && ./gutenberg/node_modules/hermes-engine/`node -e \"const platform=require('os').platform();console.log(platform === 'darwin' ? 'osx-bin' : (platform === 'linux' ? 'linux64-bin' : (platform === 'win32' ? 'win64-bin' : 'unsupported-os')));\"`/hermes -emit-binary -O -out bundle/android/App.js bundle/android/App.text.js -output-source-map", "bundle:ios": "mkdir -p bundle/ios && npm run rn-bundle -- --platform ios --dev false --entry-file index.js --assets-dest bundle/ios --bundle-output bundle/ios/App.js --sourcemap-output bundle/ios/App.js.map", "i18n-cache": "node i18n-cache/index.js", "i18n-cache:force": "cross-env REFRESH_I18N_CACHE=1 node i18n-cache/index.js", @@ -102,22 +102,16 @@ "ios:fast": "react-native run-ios", "test": "cross-env NODE_ENV=test jest --verbose --config ../../test/native/jest.config.js", "test:debug": "cross-env NODE_ENV=test node --inspect-brk jest --runInBand --verbose --config ../../test/native/jest.config.js", - "device-tests": "cross-env NODE_ENV=test jest --forceExit --detectOpenHandles --no-cache --maxWorkers=3 --reporters=default --reporters=jest-junit --verbose --config ../../test/native/jest_ui.config.js", - "device-tests-canary": "cross-env NODE_ENV=test jest --forceExit --detectOpenHandles --no-cache --maxWorkers=2 --testNamePattern=@canary --reporters=default --reporters=jest-junit --verbose --config ../../test/native/jest_ui.config.js", - "device-tests:local": "cross-env NODE_ENV=test jest --runInBand --reporters=default --reporters=jest-junit --detectOpenHandles --verbose --forceExit --config ../../test/native/jest_ui.config.js", - "device-tests:debug": "cross-env NODE_ENV=test node $NODE_DEBUG_OPTION --inspect-brk node_modules/jest/bin/jest --runInBand --reporters=default --reporters=jest-junit --detectOpenHandles --verbose --config ../../test/native/jest_ui.config.js", - "test:e2e": "npm run test:e2e:android && npm run test:e2e:ios", - "test:e2e:android": "TEST_RN_PLATFORM=android npm run device-tests", - "test:e2e:android:debug": "TEST_RN_PLATFORM=android npm run device-tests:debug", - "test:e2e:ios": "TEST_RN_PLATFORM=ios npm run device-tests", - "test:e2e:android:local": "npm run test:e2e:build-app:android && npm run test:e2e:install-app:android && TEST_RN_PLATFORM=android npm run device-tests:local", - "test:e2e:android:local:debug": "npm run test:e2e:build-app:android && npm run test:e2e:install-app:android && npm run test:e2e:android:debug", - "test:e2e:ios:local": "npm run test:e2e:bundle:ios && npm run test:e2e:build-app:ios && TEST_RN_PLATFORM=ios npm run device-tests:local", + "device-tests": "cross-env NODE_ENV=test jest --forceExit --detectOpenHandles --no-cache --maxWorkers=3 --verbose --config ./jest_ui.config.js", + "device-tests-canary": "cross-env NODE_ENV=test jest --forceExit --detectOpenHandles --no-cache --maxWorkers=2 --testNamePattern=@canary --verbose --config ./jest_ui.config.js", + "device-tests:local": "cross-env NODE_ENV=test jest --runInBand --detectOpenHandles --verbose --forceExit --config ./jest_ui.config.js", + "device-tests:debug": "cross-env NODE_ENV=test node $NODE_DEBUG_OPTION --inspect-brk node_modules/jest/bin/jest --runInBand --detectOpenHandles --verbose --config ./jest_ui.config.js", "test:e2e:bundle:android": "mkdir -p android/app/src/main/assets && npm run rn-bundle -- --reset-cache --platform android --dev false --minify false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res", - "test:e2e:build-app:android": "npm run test:e2e:bundle:android && cd android && ./gradlew clean && ./gradlew assembleDebug", - "test:e2e:install-app:android": "cd android && ./gradlew installDebug", + "test:e2e:build-app:android": "cd android && ./gradlew clean && ./gradlew assembleDebug", + "test:e2e:android:local": "npm run test:e2e:bundle:android && npm run test:e2e:build-app:android && TEST_RN_PLATFORM=android npm run device-tests:local", "test:e2e:bundle:ios": "mkdir -p ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app && npm run rn-bundle -- --reset-cache --platform=ios --dev=false --minify false --entry-file=index.js --bundle-output=./ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app/main.jsbundle --assets-dest=./ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app", - "test:e2e:build-app:ios": "npm run ios -- --configuration Release --no-packager --simulator 'iPhone 11 (13.4)'", + "test:e2e:build-app:ios": "npm run preios && SKIP_BUNDLING=true xcodebuild -workspace ios/GutenbergDemo.xcworkspace -configuration Release -scheme GutenbergDemo -destination 'platform=iOS Simulator,name=iPhone 11' -derivedDataPath ios/build/GutenbergDemo", + "test:e2e:ios:local": "npm run test:e2e:bundle:ios && npm run test:e2e:build-app:ios && TEST_RN_PLATFORM=ios npm run device-tests:local", "build:gutenberg": "cd gutenberg && npm ci && npm run build", "clean": "npm run clean:build-artifacts; npm run clean:aztec; npm run clean:haste; npm run clean:jest; npm run clean:metro; npm run clean:react; npm run clean:watchman", "clean:runtime": "npm run clean:haste; npm run clean:react; npm run clean:metro; npm run clean:jest; npm run clean:watchman; npm run clean:babel-cache", diff --git a/packages/react-native-editor/sass-transformer.js b/packages/react-native-editor/sass-transformer.js index 978c645692b24c..eb471eb024fac4 100644 --- a/packages/react-native-editor/sass-transformer.js +++ b/packages/react-native-editor/sass-transformer.js @@ -36,7 +36,7 @@ const fs = require( 'fs' ); const path = require( 'path' ); // eslint-disable-next-line import/no-extraneous-dependencies -const sass = require( 'node-sass' ); +const sass = require( 'sass' ); // eslint-disable-next-line import/no-extraneous-dependencies const css2rn = require( 'css-to-react-native-transform' ).default; @@ -48,9 +48,9 @@ const autoImportIncludePaths = [ path.join( path.dirname( __filename ), '../base-styles' ), ]; const autoImportAssets = [ + '_variables.scss', '_colors.scss', '_breakpoints.scss', - '_variables.scss', '_native.scss', '_mixins.scss', '_animations.scss', diff --git a/packages/react-native-editor/src/initial-html.js b/packages/react-native-editor/src/initial-html.js index 65556cc8ca035c..fa8cd9e285f255 100644 --- a/packages/react-native-editor/src/initial-html.js +++ b/packages/react-native-editor/src/initial-html.js @@ -1,4 +1,6 @@ export default ` +<!-- wp:file /--> + <!-- wp:heading --> <h2>Text Blocks</h2> <!-- /wp:heading --> diff --git a/packages/redux-routine/package.json b/packages/redux-routine/package.json index aa669a5429444d..82fee38d4aacc3 100644 --- a/packages/redux-routine/package.json +++ b/packages/redux-routine/package.json @@ -25,7 +25,7 @@ "react-native": "src/index", "sideEffects": false, "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "is-promise": "^4.0.0", "lodash": "^4.17.19", "rungen": "^0.3.2" diff --git a/packages/reusable-blocks/CHANGELOG.md b/packages/reusable-blocks/CHANGELOG.md index 6ef343cce6559b..1245f57a2a7928 100644 --- a/packages/reusable-blocks/CHANGELOG.md +++ b/packages/reusable-blocks/CHANGELOG.md @@ -2,8 +2,12 @@ ## Unreleased +### New Feature + +- Added a store definition `store` for the reusable blocks namespace to use with `@wordpress/data` API ([#26655](https://github.com/WordPress/gutenberg/pull/26655)). + ## 1.0.0 (2020-10-19) ### Internal -- Reusable block utilities moved from `@wordpress/editor` to `@wordpress/reusable-blocks`. +- Reusable block utilities moved from `@wordpress/editor` to `@wordpress/reusable-blocks`. diff --git a/packages/reusable-blocks/README.md b/packages/reusable-blocks/README.md index 8d6848c93e041c..140e18ef70ed9b 100644 --- a/packages/reusable-blocks/README.md +++ b/packages/reusable-blocks/README.md @@ -72,8 +72,10 @@ return ( This package also provides convenient utilities for managing reusable blocks through redux actions: ```js +import { store as reusableBlocksStore } from '@wordpress/reusable-blocks'; + function MyConvertToStaticButton( { clientId } ) { - const { __experimentalConvertBlockToStatic } = useDispatch( 'core/reusable-blocks' ); + const { __experimentalConvertBlockToStatic } = useDispatch( reusableBlocksStore ); return ( <button onClick={() => __experimentalConvertBlockToStatic( clientId )} > Convert to static @@ -82,7 +84,7 @@ function MyConvertToStaticButton( { clientId } ) { } function MyConvertToReusableButton( { clientId } ) { - const { __experimentalConvertBlocksToReusable } = useDispatch( 'core/reusable-blocks' ); + const { __experimentalConvertBlocksToReusable } = useDispatch( reusableBlocksStore ); return ( <button onClick={() => __experimentalConvertBlocksToReusable( [ clientId ] )} > Convert to reusable @@ -91,7 +93,7 @@ function MyConvertToReusableButton( { clientId } ) { } function MyDeleteReusableBlockButton( { id } ) { - const { __experimentalDeleteReusableBlock } = useDispatch( 'core/reusable-blocks' ); + const { __experimentalDeleteReusableBlock } = useDispatch( reusableBlocksStore ); return ( <button onClick={() => __experimentalDeleteReusableBlock( id )} > Delete reusable block diff --git a/packages/reusable-blocks/package.json b/packages/reusable-blocks/package.json index ae0b8669eaf06c..bb69b26aad4f01 100644 --- a/packages/reusable-blocks/package.json +++ b/packages/reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/reusable-blocks", - "version": "1.0.3", + "version": "1.0.4", "description": "Reusable blocks utilities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -22,8 +22,7 @@ "module": "build-module/index.js", "react-native": "src/index", "sideEffects": [ - "build-style/**", - "!((src|build|build-module)/(components|utils)/**)" + "{src,build,build-module}/{index.js,store/index.js}" ], "dependencies": { "@wordpress/block-editor": "file:../block-editor", @@ -36,6 +35,7 @@ "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", "@wordpress/notices": "file:../notices", + "@wordpress/url": "file:../url", "lodash": "^4.17.19" }, "publishConfig": { diff --git a/packages/reusable-blocks/src/components/reusable-blocks-menu-items/index.js b/packages/reusable-blocks/src/components/reusable-blocks-menu-items/index.js index ed68dd7fec7717..ce5883402896de 100644 --- a/packages/reusable-blocks/src/components/reusable-blocks-menu-items/index.js +++ b/packages/reusable-blocks/src/components/reusable-blocks-menu-items/index.js @@ -7,7 +7,7 @@ import { withSelect } from '@wordpress/data'; * Internal dependencies */ import ReusableBlockConvertButton from './reusable-block-convert-button'; -import ReusableBlockDeleteButton from './reusable-block-delete-button'; +import ReusableBlocksManageButton from './reusable-blocks-manage-button'; function ReusableBlocksMenuItems( { clientIds, rootClientId } ) { return ( @@ -17,7 +17,7 @@ function ReusableBlocksMenuItems( { clientIds, rootClientId } ) { rootClientId={ rootClientId } /> { clientIds.length === 1 && ( - <ReusableBlockDeleteButton clientId={ clientIds[ 0 ] } /> + <ReusableBlocksManageButton clientId={ clientIds[ 0 ] } /> ) } </> ); diff --git a/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-convert-button.js b/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-convert-button.js index 6c88b881152b58..5b34cc3a850dfc 100644 --- a/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-convert-button.js +++ b/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-convert-button.js @@ -8,11 +8,12 @@ import { MenuItem } from '@wordpress/components'; import { reusableBlock } from '@wordpress/icons'; import { useDispatch, useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies */ -import { STORE_KEY } from '../../store/constants'; +import { store } from '../../store'; /** * Menu control to convert block(s) to reusable block. @@ -69,10 +70,10 @@ export default function ReusableBlockConvertButton( { const { __experimentalConvertBlocksToReusable: convertBlocksToReusable, - } = useDispatch( STORE_KEY ); + } = useDispatch( store ); const { createSuccessNotice, createErrorNotice } = useDispatch( - 'core/notices' + noticesStore ); const onConvert = useCallback( async function () { diff --git a/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-delete-button.js b/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-delete-button.js deleted file mode 100644 index bc59105d2e44b3..00000000000000 --- a/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-delete-button.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * WordPress dependencies - */ -import { MenuItem } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { isReusableBlock } from '@wordpress/blocks'; -import { useSelect, useDispatch } from '@wordpress/data'; -import { useCallback } from '@wordpress/element'; -import { BlockSettingsMenuControls } from '@wordpress/block-editor'; - -/** - * Internal dependencies - */ -import { STORE_KEY } from '../../store/constants'; - -function ReusableBlockDeleteButton( { clientId } ) { - const { isVisible, isDisabled, block } = useSelect( - ( select ) => { - const { getBlock } = select( 'core/block-editor' ); - const { canUser } = select( 'core' ); - const blockObj = getBlock( clientId ); - - const reusableBlock = - blockObj && isReusableBlock( blockObj ) - ? select( 'core' ).getEntityRecord( - 'postType', - 'wp_block', - blockObj.attributes.ref - ) - : null; - - return { - block: blockObj, - isVisible: - !! reusableBlock && - ( reusableBlock.isTemporary || - !! canUser( 'delete', 'blocks', reusableBlock.id ) ), - isDisabled: reusableBlock && reusableBlock.isTemporary, - }; - }, - [ clientId ] - ); - - const { - __experimentalDeleteReusableBlock: deleteReusableBlock, - } = useDispatch( STORE_KEY ); - - const { createSuccessNotice, createErrorNotice } = useDispatch( - 'core/notices' - ); - const onDelete = useCallback( - async function () { - try { - await deleteReusableBlock( block.attributes.ref ); - createSuccessNotice( __( 'Block deleted.' ), { - type: 'snackbar', - } ); - } catch ( error ) { - createErrorNotice( error.message, { - type: 'snackbar', - } ); - } - }, - [ block ] - ); - - if ( ! isVisible ) { - return null; - } - - return ( - <BlockSettingsMenuControls> - { ( { onClose } ) => ( - <MenuItem - disabled={ isDisabled } - onClick={ () => { - // eslint-disable-next-line no-alert - const hasConfirmed = window.confirm( - // eslint-disable-next-line @wordpress/i18n-no-collapsible-whitespace - __( - 'Are you sure you want to delete this Reusable Block?\n\n' + - 'It will be permanently removed from all posts and pages that use it.' - ) - ); - if ( hasConfirmed ) { - onDelete(); - onClose(); - } - } } - > - { __( 'Remove from Reusable blocks' ) } - </MenuItem> - ) } - </BlockSettingsMenuControls> - ); -} - -export default ReusableBlockDeleteButton; diff --git a/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-blocks-manage-button.js b/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-blocks-manage-button.js new file mode 100644 index 00000000000000..ec4df75146bc08 --- /dev/null +++ b/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-blocks-manage-button.js @@ -0,0 +1,47 @@ +/** + * WordPress dependencies + */ +import { MenuItem } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { isReusableBlock } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; +import { BlockSettingsMenuControls } from '@wordpress/block-editor'; +import { addQueryArgs } from '@wordpress/url'; + +function ReusableBlocksManageButton( { clientId } ) { + const { isVisible } = useSelect( + ( select ) => { + const { getBlock } = select( 'core/block-editor' ); + const { canUser } = select( 'core' ); + const reusableBlock = getBlock( clientId ); + + return { + isVisible: + !! reusableBlock && + isReusableBlock( reusableBlock ) && + !! canUser( + 'update', + 'blocks', + reusableBlock.attributes.ref + ), + }; + }, + [ clientId ] + ); + + if ( ! isVisible ) { + return null; + } + + return ( + <BlockSettingsMenuControls> + <MenuItem + href={ addQueryArgs( 'edit.php', { post_type: 'wp_block' } ) } + > + { __( 'Manage Reusable blocks' ) } + </MenuItem> + </BlockSettingsMenuControls> + ); +} + +export default ReusableBlocksManageButton; diff --git a/packages/reusable-blocks/src/index.js b/packages/reusable-blocks/src/index.js index 55ba8837aa1512..2c29c97f829920 100644 --- a/packages/reusable-blocks/src/index.js +++ b/packages/reusable-blocks/src/index.js @@ -3,11 +3,6 @@ */ import '@wordpress/block-editor'; import '@wordpress/core-data'; -import '@wordpress/notices'; - -/** - * Internal dependencies - */ -import './store'; +export { store } from './store'; export * from './components'; diff --git a/packages/reusable-blocks/src/index.native.js b/packages/reusable-blocks/src/index.native.js index 14c1977326fd52..2e29e8c63d8969 100644 --- a/packages/reusable-blocks/src/index.native.js +++ b/packages/reusable-blocks/src/index.native.js @@ -3,9 +3,5 @@ */ import '@wordpress/core-data'; -/** - * Internal dependencies - */ -import './store'; - +export { store } from './store'; export * from './components'; diff --git a/packages/reusable-blocks/src/store/constants.js b/packages/reusable-blocks/src/store/constants.js deleted file mode 100644 index 3563b7456c6c78..00000000000000 --- a/packages/reusable-blocks/src/store/constants.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Constant for the store module (or reducer) key. - * - * @type {string} - */ -export const STORE_KEY = 'core/reusable-blocks'; diff --git a/packages/reusable-blocks/src/store/controls.js b/packages/reusable-blocks/src/store/controls.js index 8b9b9613c05217..308cb68934338c 100644 --- a/packages/reusable-blocks/src/store/controls.js +++ b/packages/reusable-blocks/src/store/controls.js @@ -10,6 +10,11 @@ import { import { createRegistryControl } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import { store as reusableBlocksStore } from './index.js'; + /** * Convert a reusable block to a static block effect handler * @@ -94,7 +99,7 @@ const controls = { .dispatch( 'core/block-editor' ) .replaceBlocks( clientIds, newBlock ); registry - .dispatch( 'core/reusable-blocks' ) + .dispatch( reusableBlocksStore ) .__experimentalSetEditingReusableBlock( newBlock.clientId, true diff --git a/packages/reusable-blocks/src/store/index.js b/packages/reusable-blocks/src/store/index.js index b6b212ad968a88..46a38b67f8b8f7 100644 --- a/packages/reusable-blocks/src/store/index.js +++ b/packages/reusable-blocks/src/store/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { registerStore } from '@wordpress/data'; +import { createReduxStore, register } from '@wordpress/data'; /** * Internal dependencies @@ -10,18 +10,21 @@ import * as actions from './actions'; import controls from './controls'; import reducer from './reducer'; import * as selectors from './selectors'; -import { STORE_KEY } from './constants'; + +const STORE_NAME = 'core/reusable-blocks'; /** - * Data store configuration. + * Store definition for the reusable blocks namespace. * - * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#registerStore + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore * * @type {Object} */ -export default registerStore( STORE_KEY, { +export const store = createReduxStore( STORE_NAME, { actions, controls, reducer, selectors, } ); + +register( store ); diff --git a/packages/rich-text/CHANGELOG.md b/packages/rich-text/CHANGELOG.md index 3e00fe0a138461..1d9457496ed6dd 100644 --- a/packages/rich-text/CHANGELOG.md +++ b/packages/rich-text/CHANGELOG.md @@ -2,33 +2,37 @@ ## Unreleased +### New Feature + +- Added a store definition `store` for the rich-text namespace to use with `@wordpress/data` API ([#26655](https://github.com/WordPress/gutenberg/pull/26655)). + ## 3.3.0 (2019-05-21) ### Internal -- Removed and renamed undocumented functions and constants: - * Removed `charAt` - * Removed `getSelectionStart` - * Removed `getSelectionEnd` - * Removed `insertLineBreak` - * Renamed `isEmptyLine` to `__unstableIsEmptyLine` - * Renamed `insertLineSeparator` to `__unstableInsertLineSeparator` - * Renamed `apply` to `__unstableApply` - * Renamed `unstableToDom` to `__unstableToDom` - * Renamed `LINE_SEPARATOR` to `__UNSTABLE_LINE_SEPARATOR` - * Renamed `indentListItems` to `__unstableIndentListItems` - * Renamed `outdentListItems` to `__unstableOutdentListItems` - * Renamed `changeListType` to `__unstableChangeListType` +- Removed and renamed undocumented functions and constants: + - Removed `charAt` + - Removed `getSelectionStart` + - Removed `getSelectionEnd` + - Removed `insertLineBreak` + - Renamed `isEmptyLine` to `__unstableIsEmptyLine` + - Renamed `insertLineSeparator` to `__unstableInsertLineSeparator` + - Renamed `apply` to `__unstableApply` + - Renamed `unstableToDom` to `__unstableToDom` + - Renamed `LINE_SEPARATOR` to `__UNSTABLE_LINE_SEPARATOR` + - Renamed `indentListItems` to `__unstableIndentListItems` + - Renamed `outdentListItems` to `__unstableOutdentListItems` + - Renamed `changeListType` to `__unstableChangeListType` ## 3.1.0 (2019-03-06) ### Enhancement -- Added format boundaries. -- Removed parameters from `create` to filter out content. -- Removed the `createLinePadding` from `apply`, which is now built in. -- Improved format placeholder. -- Improved dom diffing. +- Added format boundaries. +- Removed parameters from `create` to filter out content. +- Removed the `createLinePadding` from `apply`, which is now built in. +- Improved format placeholder. +- Improved dom diffing. ## 3.0.4 (2019-01-03) @@ -36,7 +40,7 @@ ### Internal -- Internal performance optimizations to avoid excessive expensive creation of DOM documents. +- Internal performance optimizations to avoid excessive expensive creation of DOM documents. ## 3.0.2 (2018-11-21) @@ -46,7 +50,7 @@ ### Breaking Changes -- `toHTMLString` always expects an object instead of multiple arguments. +- `toHTMLString` always expects an object instead of multiple arguments. ## 2.0.4 (2018-11-09) @@ -54,8 +58,8 @@ ### Bug Fix -- Fix Format Type Assignment During Parsing. -- Fix applying formats on multiline values without wrapper tags. +- Fix Format Type Assignment During Parsing. +- Fix applying formats on multiline values without wrapper tags. ## 2.0.2 (2018-11-03) @@ -63,7 +67,7 @@ ## 2.0.0 (2018-10-30) -- Remove `@wordpress/blocks` as a dependency. +- Remove `@wordpress/blocks` as a dependency. ## 1.0.2 (2018-10-29) @@ -71,4 +75,4 @@ ## 1.0.0 (2018-10-18) -- Initial release. +- Initial release. diff --git a/packages/rich-text/README.md b/packages/rich-text/README.md index df8d0d62fc626e..766eb4566de490 100644 --- a/packages/rich-text/README.md +++ b/packages/rich-text/README.md @@ -302,6 +302,18 @@ _Returns_ - `Array<RichTextValue>`: An array of new values. +<a name="store" href="#store">#</a> **store** + +Store definition for the rich-text namespace. + +_Related_ + +- <https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore> + +_Type_ + +- `Object` + <a name="toggleFormat" href="#toggleFormat">#</a> **toggleFormat** Toggles a format object to a Rich Text value at the current selection. @@ -343,6 +355,24 @@ _Returns_ - `(RichTextFormatType|undefined)`: The previous format value, if it has been successfully unregistered; otherwise `undefined`. +<a name="useAnchorRef" href="#useAnchorRef">#</a> **useAnchorRef** + +This hook, to be used in a format type's Edit component, returns the active +element that is formatted, or the selection range if no format is active. +The returned value is meant to be used for positioning UI, e.g. by passing it +to the `Popover` component. + +_Parameters_ + +- _$1_ `Object`: Named parameters. +- _$1.ref_ `RefObject<HTMLElement>`: React ref of the element containing the editable content. +- _$1.value_ `RichTextValue`: Value to check for selection. +- _$1.settings_ `RichTextFormatType`: The format type's settings. + +_Returns_ + +- `(Element|Range)`: The active element or selection range. + <!-- END TOKEN(Autogenerated API docs) --> diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 6e7df799ab95de..56ec1464e79733 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -22,13 +22,15 @@ "module": "build-module/index.js", "react-native": "src/index", "sideEffects": [ - "!((src|build|build-module)/component/**)" + "src/**/*.scss", + "{src,build,build-module}/{index.js,store/index.js}" ], "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", "@wordpress/element": "file:../element", "@wordpress/escape-html": "file:../escape-html", "@wordpress/is-shallow-equal": "file:../is-shallow-equal", diff --git a/packages/rich-text/src/component/format-edit.js b/packages/rich-text/src/component/format-edit.js index 753e6a02739b57..893b0af9a5c2da 100644 --- a/packages/rich-text/src/component/format-edit.js +++ b/packages/rich-text/src/component/format-edit.js @@ -30,8 +30,11 @@ export default function FormatEdit( { value, allowedFormats, withoutInteractiveFormatting, + forwardedRef, } ) { - return formatTypes.map( ( { name, edit: Edit, tagName } ) => { + return formatTypes.map( ( settings ) => { + const { name, edit: Edit, tagName } = settings; + if ( ! Edit ) { return null; } @@ -67,6 +70,7 @@ export default function FormatEdit( { value={ value } onChange={ onChange } onFocus={ onFocus } + contentRef={ forwardedRef } /> ); } ); diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 04fcb77f92b606..625da9012fd05c 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { find, isNil } from 'lodash'; - /** * WordPress dependencies */ @@ -24,6 +19,7 @@ import { ESCAPE, } from '@wordpress/keycodes'; import deprecated from '@wordpress/deprecated'; +import { getFilesFromDataTransfer } from '@wordpress/dom'; /** * Internal dependencies @@ -324,13 +320,7 @@ function RichText( return; } - const clipboardData = event.clipboardData; - let { items, files } = clipboardData; - - // In Edge these properties can be null instead of undefined, so a more - // rigorous test is required over using default values. - items = isNil( items ) ? [] : items; - files = isNil( files ) ? [] : files; + const { clipboardData } = event; let plainText = ''; let html = ''; @@ -384,32 +374,14 @@ function RichText( } if ( onPaste ) { - files = Array.from( files ); - - Array.from( items ).forEach( ( item ) => { - if ( ! item.getAsFile ) { - return; - } - - const file = item.getAsFile(); - - if ( ! file ) { - return; - } - - const { name, type, size } = file; - - if ( ! find( files, { name, type, size } ) ) { - files.push( file ); - } - } ); + const files = getFilesFromDataTransfer( clipboardData ); onPaste( { value: removeEditorOnlyFormats( record.current ), onChange: handleChange, html, plainText, - files, + files: [ ...files ], activeFormats, } ); } @@ -1138,6 +1110,7 @@ function RichText( onChange={ handleChange } onFocus={ focus } formatTypes={ formatTypes } + forwardedRef={ ref } /> ) } { children && diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js index 8116493bb7ac25..7e59d11c094181 100644 --- a/packages/rich-text/src/component/index.native.js +++ b/packages/rich-text/src/component/index.native.js @@ -825,9 +825,14 @@ export class RichText extends Component { maxWidth && this.state.width && maxWidth - this.state.width < 10 ? maxWidth : this.state.width; + const containerStyles = style?.padding && + style?.backgroundColor && { + padding: style.padding, + backgroundColor: style.backgroundColor, + }; return ( - <View> + <View style={ containerStyles }> { children && children( { isSelected, diff --git a/packages/rich-text/src/component/use-anchor-ref.js b/packages/rich-text/src/component/use-anchor-ref.js new file mode 100644 index 00000000000000..3e9dc8554defea --- /dev/null +++ b/packages/rich-text/src/component/use-anchor-ref.js @@ -0,0 +1,61 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { getActiveFormat } from '../get-active-format'; + +/** @typedef {import('@wordpress/element').RefObject} RefObject */ +/** @typedef {import('../register-format-type').RichTextFormatType} RichTextFormatType */ +/** @typedef {import('../create').RichTextValue} RichTextValue */ + +/** + * This hook, to be used in a format type's Edit component, returns the active + * element that is formatted, or the selection range if no format is active. + * The returned value is meant to be used for positioning UI, e.g. by passing it + * to the `Popover` component. + * + * @param {Object} $1 Named parameters. + * @param {RefObject<HTMLElement>} $1.ref React ref of the element + * containing the editable content. + * @param {RichTextValue} $1.value Value to check for selection. + * @param {RichTextFormatType} $1.settings The format type's settings. + * + * @return {Element|Range} The active element or selection range. + */ +export function useAnchorRef( { ref, value, settings = {} } ) { + const { tagName, className, name } = settings; + const activeFormat = name ? getActiveFormat( value, name ) : undefined; + + return useMemo( () => { + const { ownerDocument } = ref.current; + const { defaultView } = ownerDocument; + const selection = defaultView.getSelection(); + + if ( ! selection.rangeCount ) { + return; + } + + const range = selection.getRangeAt( 0 ); + + if ( ! activeFormat ) { + return range; + } + + let element = range.startContainer; + + // If the caret is right before the element, select the next element. + element = element.nextElementSibling || element; + + while ( element.nodeType !== element.ELEMENT_NODE ) { + element = element.parentNode; + } + + return element.closest( + tagName + ( className ? '.' + className : '' ) + ); + }, [ activeFormat, value.start, value.end, tagName, className ] ); +} diff --git a/packages/rich-text/src/index.js b/packages/rich-text/src/index.js index ac4cea67d4757c..18791abde00a10 100644 --- a/packages/rich-text/src/index.js +++ b/packages/rich-text/src/index.js @@ -1,8 +1,4 @@ -/** - * Internal dependencies - */ -import './store'; - +export { store } from './store'; export { applyFormat } from './apply-format'; export { concat } from './concat'; export { create } from './create'; @@ -36,5 +32,7 @@ export { outdentListItems as __unstableOutdentListItems } from './outdent-list-i export { changeListType as __unstableChangeListType } from './change-list-type'; export { createElement as __unstableCreateElement } from './create-element'; +export { useAnchorRef } from './component/use-anchor-ref'; + export { default as __experimentalRichText } from './component'; export { default as __unstableFormatEdit } from './component/format-edit'; diff --git a/packages/rich-text/src/store/index.js b/packages/rich-text/src/store/index.js index beaed276c2fd69..02b320b2a43b07 100644 --- a/packages/rich-text/src/store/index.js +++ b/packages/rich-text/src/store/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { registerStore } from '@wordpress/data'; +import { createReduxStore, register } from '@wordpress/data'; /** * Internal dependencies @@ -10,4 +10,19 @@ import reducer from './reducer'; import * as selectors from './selectors'; import * as actions from './actions'; -registerStore( 'core/rich-text', { reducer, selectors, actions } ); +const STORE_NAME = 'core/rich-text'; + +/** + * Store definition for the rich-text namespace. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore + * + * @type {Object} + */ +export const store = createReduxStore( STORE_NAME, { + reducer, + selectors, + actions, +} ); + +register( store ); diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 7a1a34d885cfa4..0b01a0d7119a6d 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +### Enhancements + +- Autoformat TypeScript files (`*.ts` and `*.tsx`) in `format-js` script (#27138)[https://github.com/WordPress/gutenberg/pull/27138]. +- The bundled `wp-prettier` dependency has been upgraded from `2.0.5` to `2.2.1`. +- The bundled Babel dependency has been upgraded from `7.11` to `7.12`. + +### Internal + +- The bundled `ignore-emit-webpack-plugin` dependency has been updated from requiring `2.0.3` to requiring `^2.0.6`. + ## 12.5.0 (2020-10-30) ### Enhancements diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index a07e74732c5c04..22a9878ac3e015 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -84,6 +84,9 @@ const config = { output: { comments: /translators:/i, }, + compress: { + passes: 2, + }, }, extractComments: false, } ), diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 872a1af17dc688..4a6fc0b253071c 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -50,20 +50,20 @@ "dir-glob": "^3.0.1", "eslint": "^7.1.0", "eslint-plugin-markdown": "^1.0.2", - "ignore-emit-webpack-plugin": "2.0.3", + "ignore-emit-webpack-plugin": "^2.0.6", "jest": "^25.3.0", "jest-puppeteer": "^4.4.0", "markdownlint": "^0.18.0", "markdownlint-cli": "^0.21.0", "mini-css-extract-plugin": "^0.9.0", "minimist": "^1.2.0", - "node-sass": "^4.13.1", "npm-package-json-lint": "^5.0.0", "postcss-loader": "^3.0.0", - "prettier": "npm:wp-prettier@2.0.5", + "prettier": "npm:wp-prettier@2.2.1-beta-1", "puppeteer": "npm:puppeteer-core@3.0.0", "read-pkg-up": "^1.0.1", "resolve-bin": "^0.4.0", + "sass": "^1.26.11", "sass-loader": "^8.0.2", "source-map-loader": "^0.2.4", "stylelint": "^13.6.0", diff --git a/packages/scripts/scripts/format-js.js b/packages/scripts/scripts/format-js.js index ad57a24a20c195..55a040e1d9355e 100644 --- a/packages/scripts/scripts/format-js.js +++ b/packages/scripts/scripts/format-js.js @@ -103,7 +103,9 @@ if ( fileArgs.length === 0 ) { } // Converts `foo/bar` directory to `foo/bar/**/*.js` -const globArgs = dirGlob( fileArgs, { extensions: [ 'js', 'jsx' ] } ); +const globArgs = dirGlob( fileArgs, { + extensions: [ 'js', 'jsx', 'ts', 'tsx' ], +} ); const result = spawn( resolveBin( 'prettier' ), diff --git a/packages/server-side-render/README.md b/packages/server-side-render/README.md index bfe883037b8159..abf1329af3bf63 100644 --- a/packages/server-side-render/README.md +++ b/packages/server-side-render/README.md @@ -127,6 +127,7 @@ If you pass `attributes` to `ServerSideRender`, the block must also be registere register_block_type( 'core/archives', array( + 'apiVersion' => 2, 'attributes' => array( 'showPostCounts' => array( 'type' => 'boolean', diff --git a/packages/server-side-render/package.json b/packages/server-side-render/package.json index 80b45c5d2ff6e9..579440bd828d7f 100644 --- a/packages/server-side-render/package.json +++ b/packages/server-side-render/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/server-side-render", - "version": "1.19.1", + "version": "1.19.2", "description": "The component used with WordPress to server-side render a preview of dynamic blocks to display in the editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -23,7 +23,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/components": "file:../components", "@wordpress/data": "file:../data", diff --git a/packages/shortcode/package.json b/packages/shortcode/package.json index 948031c53a42d2..9c94cffd26ff28 100644 --- a/packages/shortcode/package.json +++ b/packages/shortcode/package.json @@ -22,7 +22,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "lodash": "^4.17.19", "memize": "^1.1.0" }, diff --git a/packages/shortcode/src/index.js b/packages/shortcode/src/index.js index 429b2eb722175f..e1e6a62e52b46d 100644 --- a/packages/shortcode/src/index.js +++ b/packages/shortcode/src/index.js @@ -90,29 +90,23 @@ export function next( tag, text, index = 0 ) { * @return {string} Text with shortcodes replaced. */ export function replace( tag, text, callback ) { - return text.replace( regexp( tag ), function ( - match, - left, - $3, - attrs, - slash, - content, - closing, - right - ) { - // If both extra brackets exist, the shortcode has been properly - // escaped. - if ( left === '[' && right === ']' ) { - return match; - } + return text.replace( + regexp( tag ), + function ( match, left, $3, attrs, slash, content, closing, right ) { + // If both extra brackets exist, the shortcode has been properly + // escaped. + if ( left === '[' && right === ']' ) { + return match; + } - // Create the match object and pass it through the callback. - const result = callback( fromMatch( arguments ) ); + // Create the match object and pass it through the callback. + const result = callback( fromMatch( arguments ) ); - // Make sure to return any of the extra brackets if they weren't used to - // escape the shortcode. - return result ? left + result + right : match; - } ); + // Make sure to return any of the extra brackets if they weren't used to + // escape the shortcode. + return result || result === '' ? left + result + right : match; + } + ); } /** diff --git a/packages/shortcode/src/test/index.js b/packages/shortcode/src/test/index.js index 6d2af91ee70b47..e385dacb0870b0 100644 --- a/packages/shortcode/src/test/index.js +++ b/packages/shortcode/src/test/index.js @@ -219,6 +219,30 @@ describe( 'shortcode', () => { ); expect( result2 ).toBe( 'this has the [foo] shortcode' ); } ); + + it( 'should replace shortcode with a number', () => { + const result1 = replace( 'foo', 'hello [foo] world', () => 3 ); + expect( result1 ).toBe( 'hello 3 world' ); + + const result2 = replace( + 'foo', + 'hello [foo bar=bar baz="baz" qux]delete me[/foo] world', + () => 4 + ); + expect( result2 ).toBe( 'hello 4 world' ); + } ); + + it( 'should replace shortcode with an empty string', () => { + const result1 = replace( 'foo', 'hello [foo] world', () => '' ); + expect( result1 ).toBe( 'hello world' ); + + const result2 = replace( + 'foo', + 'hello [foo bar=bar baz="baz" qux]delete me[/foo] world', + () => '' + ); + expect( result2 ).toBe( 'hello world' ); + } ); } ); describe( 'attrs', () => { diff --git a/packages/side-effects.md b/packages/side-effects.md index d55742ff080878..78bb144bdac98f 100644 --- a/packages/side-effects.md +++ b/packages/side-effects.md @@ -9,8 +9,8 @@ Here is an example: ```js import { registerStore } from '@wordpress/data'; -const store = registerStore( STORE_KEY, { - // ... +const store = registerStore( STORE_NAME, { + // ... } ); ``` @@ -22,7 +22,7 @@ However, if this were to happen inside of an `init` function that doesn't get ca import { registerStore } from '@wordpress/data'; export function init() { - const store = registerStore( STORE_KEY, { + const store = registerStore( STORE_NAME, { // ... } ); } @@ -85,20 +85,4 @@ If it has a few files with side effects, it can list them: } ``` -This allows the bundler to assume that only the modules that were declared have side effects, and *nothing else does*. Of course, this means that we need to be careful to include everything that *does* have side effects, or problems can arise in applications that make use of the package. - -## The approach in `@wordpress` - -In order to reduce maintenance cost and minimize the chance of breakage, we opted for using inverse globs for a number of `@wordpress` packages, where we list the paths that *do not* include side effects, leaving the bundler to assume that everything else does. Here's an example: - -```json -{ - "sideEffects": [ - "!((src|build|build-module)/(components|utils)/**)" - ], -} -``` - -The above means that the bundler should assume that anything outside the `components` and `utils` directories contains side effects, and nothing in those directories does. These directories can be inside of a `src`, `build`, or `build-module` top-level directory in the package, due to the way `@wordpress` packages are built. - -This approach should guarantee that everything in `components` and `utils` can be tree-shaken. It will only potentially cause problems if one of the files in there uses side effects, which would be a bad practice for a component or utility file. +This allows the bundler to assume that only the modules that were declared have side effects, and _nothing else does_. Of course, this means that we need to be careful to include everything that _does_ have side effects, or problems can arise in applications that make use of the package. diff --git a/packages/token-list/package.json b/packages/token-list/package.json index 8ca60e3892d460..6b2b96c03834f3 100644 --- a/packages/token-list/package.json +++ b/packages/token-list/package.json @@ -23,7 +23,7 @@ "react-native": "src/index", "types": "build-types", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "lodash": "^4.17.19" }, "publishConfig": { diff --git a/packages/url/README.md b/packages/url/README.md index be964361ac1def..c0df7acf89e227 100644 --- a/packages/url/README.md +++ b/packages/url/README.md @@ -37,6 +37,36 @@ _Returns_ - `string`: URL with arguments applied. +<a name="buildQueryString" href="#buildQueryString">#</a> **buildQueryString** + +Generates URL-encoded query string using input query data. + +It is intended to behave equivalent as PHP's `http_build_query`, configured +with encoding type PHP_QUERY_RFC3986 (spaces as `%20`). + +_Usage_ + +```js +const queryString = buildQueryString( { + simple: 'is ok', + arrays: [ 'are', 'fine', 'too' ], + objects: { + evenNested: { + ok: 'yes', + }, + }, +} ); +// "simple=is%20ok&arrays%5B0%5D=are&arrays%5B1%5D=fine&arrays%5B2%5D=too&objects%5BevenNested%5D%5Bok%5D=yes" +``` + +_Parameters_ + +- _data_ `Record<string,*>`: Data to encode. + +_Returns_ + +- `string`: Query string. + <a name="cleanForSlug" href="#cleanForSlug">#</a> **cleanForSlug** Performs some basic cleanup of a string for use as a post slug. @@ -188,7 +218,27 @@ _Parameters_ _Returns_ -- `(QueryArgParsed|undefined)`: Query arg value. +- `(QueryArgParsed|void)`: Query arg value. + +<a name="getQueryArgs" href="#getQueryArgs">#</a> **getQueryArgs** + +Returns an object of query arguments of the given URL. If the given URL is +invalid or has no querystring, an empty object is returned. + +_Usage_ + +```js +const foo = getQueryArgs( 'https://wordpress.org?foo=bar&bar=baz' ); +// { "foo": "bar", "bar": "baz" } +``` + +_Parameters_ + +- _url_ `string`: URL. + +_Returns_ + +- `QueryArgs`: Query args object. <a name="getQueryString" href="#getQueryString">#</a> **getQueryString** diff --git a/packages/url/package.json b/packages/url/package.json index 01d14070995d0c..1148ba38d114ed 100644 --- a/packages/url/package.json +++ b/packages/url/package.json @@ -24,9 +24,8 @@ "types": "build-types", "sideEffects": false, "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "lodash": "^4.17.19", - "qs": "^6.5.2", "react-native-url-polyfill": "^1.1.2" }, "publishConfig": { diff --git a/packages/url/src/add-query-args.js b/packages/url/src/add-query-args.js index 2d48c0be5a06b8..859785d44544a7 100644 --- a/packages/url/src/add-query-args.js +++ b/packages/url/src/add-query-args.js @@ -1,7 +1,8 @@ /** - * External dependencies + * Internal dependencies */ -import { parse, stringify } from 'qs'; +import { getQueryArgs } from './get-query-args'; +import { buildQueryString } from './build-query-string'; /** * Appends arguments as querystring to the provided URL. If the URL already @@ -31,14 +32,11 @@ export function addQueryArgs( url = '', args ) { const queryStringIndex = url.indexOf( '?' ); if ( queryStringIndex !== -1 ) { // Merge into existing query arguments. - args = Object.assign( - parse( url.substr( queryStringIndex + 1 ) ), - args - ); + args = Object.assign( getQueryArgs( url ), args ); // Change working base URL to omit previous query arguments. baseUrl = baseUrl.substr( 0, queryStringIndex ); } - return baseUrl + '?' + stringify( args ); + return baseUrl + '?' + buildQueryString( args ); } diff --git a/packages/url/src/build-query-string.js b/packages/url/src/build-query-string.js new file mode 100644 index 00000000000000..51216a9f791d99 --- /dev/null +++ b/packages/url/src/build-query-string.js @@ -0,0 +1,61 @@ +/** + * Generates URL-encoded query string using input query data. + * + * It is intended to behave equivalent as PHP's `http_build_query`, configured + * with encoding type PHP_QUERY_RFC3986 (spaces as `%20`). + * + * @example + * ```js + * const queryString = buildQueryString( { + * simple: 'is ok', + * arrays: [ 'are', 'fine', 'too' ], + * objects: { + * evenNested: { + * ok: 'yes', + * }, + * }, + * } ); + * // "simple=is%20ok&arrays%5B0%5D=are&arrays%5B1%5D=fine&arrays%5B2%5D=too&objects%5BevenNested%5D%5Bok%5D=yes" + * ``` + * + * @param {Record<string,*>} data Data to encode. + * + * @return {string} Query string. + */ +export function buildQueryString( data ) { + let string = ''; + + const stack = Array.from( Object.entries( data ) ); + + let pair; + while ( ( pair = stack.shift() ) ) { + let [ key, value ] = pair; + + // Support building deeply nested data, from array or object values. + const hasNestedData = + Array.isArray( value ) || ( value && value.constructor === Object ); + + if ( hasNestedData ) { + // Push array or object values onto the stack as composed of their + // original key and nested index or key, retaining order by a + // combination of Array#reverse and Array#unshift onto the stack. + const valuePairs = Object.entries( value ).reverse(); + for ( const [ member, memberValue ] of valuePairs ) { + stack.unshift( [ `${ key }[${ member }]`, memberValue ] ); + } + } else if ( value !== undefined ) { + // Null is treated as special case, equivalent to empty string. + if ( value === null ) { + value = ''; + } + + string += + '&' + [ key, value ].map( encodeURIComponent ).join( '=' ); + } + } + + // Loop will concatenate with leading `&`, but it's only expected for all + // but the first query parameter. This strips the leading `&`, while still + // accounting for the case that the string may in-fact be empty. + return string.substr( 1 ); +} diff --git a/packages/url/src/get-query-arg.js b/packages/url/src/get-query-arg.js index f6e184f0798ce3..d81a6249a1c0ae 100644 --- a/packages/url/src/get-query-arg.js +++ b/packages/url/src/get-query-arg.js @@ -1,7 +1,7 @@ /** - * External dependencies + * Internal dependencies */ -import { parse } from 'qs'; +import { getQueryArgs } from './get-query-args'; /* eslint-disable jsdoc/valid-types */ /** @@ -24,14 +24,8 @@ import { parse } from 'qs'; * const foo = getQueryArg( 'https://wordpress.org?foo=bar&bar=baz', 'foo' ); // bar * ``` * - * @return {QueryArgParsed|undefined} Query arg value. + * @return {QueryArgParsed|void} Query arg value. */ export function getQueryArg( url, arg ) { - const queryStringIndex = url.indexOf( '?' ); - const query = - queryStringIndex !== -1 - ? parse( url.substr( queryStringIndex + 1 ) ) - : {}; - - return query[ arg ]; + return getQueryArgs( url )[ arg ]; } diff --git a/packages/url/src/get-query-args.js b/packages/url/src/get-query-args.js new file mode 100644 index 00000000000000..273f7f380c8ef1 --- /dev/null +++ b/packages/url/src/get-query-args.js @@ -0,0 +1,94 @@ +/** + * Internal dependencies + */ +import { getQueryString } from './get-query-string'; + +/** @typedef {import('./get-query-arg').QueryArgParsed} QueryArgParsed */ + +/** + * @typedef {Record<string,QueryArgParsed>} QueryArgs + */ + +/** + * Sets a value in object deeply by a given array of path segments. Mutates the + * object reference. + * + * @param {Record<string,*>} object Object in which to assign. + * @param {string[]} path Path segment at which to set value. + * @param {*} value Value to set. + */ +function setPath( object, path, value ) { + const length = path.length; + const lastIndex = length - 1; + for ( let i = 0; i < length; i++ ) { + let key = path[ i ]; + + if ( ! key && Array.isArray( object ) ) { + // If key is empty string and next value is array, derive key from + // the current length of the array. + key = object.length.toString(); + } + + // If the next key in the path is numeric (or empty string), it will be + // created as an array. Otherwise, it will be created as an object. + const isNextKeyArrayIndex = ! isNaN( Number( path[ i + 1 ] ) ); + + object[ key ] = + i === lastIndex + ? // If at end of path, assign the intended value. + value + : // Otherwise, advance to the next object in the path, creating + // it if it does not yet exist. + object[ key ] || ( isNextKeyArrayIndex ? [] : {} ); + + if ( Array.isArray( object[ key ] ) && ! isNextKeyArrayIndex ) { + // If we current key is non-numeric, but the next value is an + // array, coerce the value to an object. + object[ key ] = { ...object[ key ] }; + } + + // Update working reference object to the next in the path. + object = object[ key ]; + } +} + +/** + * Returns an object of query arguments of the given URL. If the given URL is + * invalid or has no querystring, an empty object is returned. + * + * @param {string} url URL. + * + * @example + * ```js + * const foo = getQueryArgs( 'https://wordpress.org?foo=bar&bar=baz' ); + * // { "foo": "bar", "bar": "baz" } + * ``` + * + * @return {QueryArgs} Query args object. + */ +export function getQueryArgs( url ) { + return ( + ( getQueryString( url ) || '' ) + // Normalize space encoding, accounting for PHP URL encoding + // corresponding to `application/x-www-form-urlencoded`. + // + // See: https://tools.ietf.org/html/rfc1866#section-8.2.1 + .replace( /\+/g, '%20' ) + .split( '&' ) + .reduce( ( accumulator, keyValue ) => { + const [ key, value = '' ] = keyValue + .split( '=' ) + // Filtering avoids decoding as `undefined` for value, where + // default is restored in destructuring assignment. + .filter( Boolean ) + .map( decodeURIComponent ); + + if ( key ) { + const segments = key.replace( /\]/g, '' ).split( '[' ); + setPath( accumulator, segments, value ); + } + + return accumulator; + }, {} ) + ); +} diff --git a/packages/url/src/get-query-string.js b/packages/url/src/get-query-string.js index e1437f4e78a131..8624ce5c6c8b9d 100644 --- a/packages/url/src/get-query-string.js +++ b/packages/url/src/get-query-string.js @@ -13,7 +13,7 @@ export function getQueryString( url ) { let query; try { - query = new URL( url ).search.substring( 1 ); + query = new URL( url, 'http://example.com' ).search.substring( 1 ); } catch ( error ) {} if ( query ) { diff --git a/packages/url/src/index.js b/packages/url/src/index.js index 5175803dfb1e9e..f060ae8152897d 100644 --- a/packages/url/src/index.js +++ b/packages/url/src/index.js @@ -7,12 +7,14 @@ export { isValidAuthority } from './is-valid-authority'; export { getPath } from './get-path'; export { isValidPath } from './is-valid-path'; export { getQueryString } from './get-query-string'; +export { buildQueryString } from './build-query-string'; export { isValidQueryString } from './is-valid-query-string'; export { getPathAndQueryString } from './get-path-and-query-string'; export { getFragment } from './get-fragment'; export { isValidFragment } from './is-valid-fragment'; export { addQueryArgs } from './add-query-args'; export { getQueryArg } from './get-query-arg'; +export { getQueryArgs } from './get-query-args'; export { hasQueryArg } from './has-query-arg'; export { removeQueryArgs } from './remove-query-args'; export { prependHTTP } from './prepend-http'; diff --git a/packages/url/src/remove-query-args.js b/packages/url/src/remove-query-args.js index 796e46e0ab980c..8d2ac9757ae305 100644 --- a/packages/url/src/remove-query-args.js +++ b/packages/url/src/remove-query-args.js @@ -1,7 +1,8 @@ /** - * External dependencies + * Internal dependencies */ -import { parse, stringify } from 'qs'; +import { getQueryArgs } from './get-query-args'; +import { buildQueryString } from './build-query-string'; /** * Removes arguments from the query string of the url @@ -18,14 +19,12 @@ import { parse, stringify } from 'qs'; */ export function removeQueryArgs( url, ...args ) { const queryStringIndex = url.indexOf( '?' ); - const query = - queryStringIndex !== -1 - ? parse( url.substr( queryStringIndex + 1 ) ) - : {}; - const baseUrl = - queryStringIndex !== -1 ? url.substr( 0, queryStringIndex ) : url; + if ( queryStringIndex === -1 ) { + return url; + } + const query = getQueryArgs( url ); + const baseURL = url.substr( 0, queryStringIndex ); args.forEach( ( arg ) => delete query[ arg ] ); - - return baseUrl + '?' + stringify( query ); + return baseURL + '?' + buildQueryString( query ); } diff --git a/packages/url/src/test/index.test.js b/packages/url/src/test/index.test.js index 9699e24ec0a067..9a2eddc478a551 100644 --- a/packages/url/src/test/index.test.js +++ b/packages/url/src/test/index.test.js @@ -16,6 +16,7 @@ import { getPath, isValidPath, getQueryString, + buildQueryString, isValidQueryString, getFragment, isValidFragment, @@ -27,6 +28,7 @@ import { safeDecodeURI, filterURLForDisplay, cleanForSlug, + getQueryArgs, } from '../'; import wptData from './fixtures/wpt-data'; @@ -288,6 +290,14 @@ describe( 'getQueryString', () => { ).toBe( 'foo=bar&foo=baz?test' ); } ); + it( 'returns the query string of a path', () => { + expect( getQueryString( '/wp-json/wp/v2/posts?type=page' ) ).toBe( + 'type=page' + ); + + expect( getQueryString( '/wp-json/wp/v2/posts' ) ).toBeUndefined(); + } ); + it( 'returns undefined when the provided does not contain a url query string', () => { expect( getQueryString( '' ) ).toBeUndefined(); expect( @@ -313,6 +323,56 @@ describe( 'getQueryString', () => { } ); } ); +describe( 'buildQueryString', () => { + it( 'builds simple strings', () => { + const data = { + foo: 'bar', + baz: 'boom', + cow: 'milk', + php: 'hypertext processor', + }; + + expect( buildQueryString( data ) ).toBe( + 'foo=bar&baz=boom&cow=milk&php=hypertext%20processor' + ); + } ); + + it( 'builds complex data', () => { + const data = { + user: { + name: 'Bob Smith', + age: 47, + sex: 'M', + dob: '5/12/1956', + }, + pastimes: [ 'golf', 'opera', 'poker', 'rap' ], + children: { + bobby: { age: 12, sex: 'M' }, + sally: { age: 8, sex: 'F' }, + }, + }; + + expect( buildQueryString( data ) ).toBe( + 'user%5Bname%5D=Bob%20Smith&user%5Bage%5D=47&user%5Bsex%5D=M&user%5Bdob%5D=5%2F12%2F1956&pastimes%5B0%5D=golf&pastimes%5B1%5D=opera&pastimes%5B2%5D=poker&pastimes%5B3%5D=rap&children%5Bbobby%5D%5Bage%5D=12&children%5Bbobby%5D%5Bsex%5D=M&children%5Bsally%5D%5Bage%5D=8&children%5Bsally%5D%5Bsex%5D=F' + ); + } ); + + it( 'builds falsey values', () => { + const data = { + empty: '', + null: null, + undefined, + zero: 0, + }; + + expect( buildQueryString( data ) ).toBe( 'empty=&null=&zero=0' ); + } ); + + it( 'builds an empty object as an empty string', () => { + expect( buildQueryString( {} ) ).toBe( '' ); + } ); +} ); + describe( 'isValidQueryString', () => { it( 'returns true if the query string is valid', () => { expect( isValidQueryString( 'test' ) ).toBe( true ); @@ -520,6 +580,116 @@ describe( 'addQueryArgs', () => { } ); } ); +describe( 'getQueryArgs', () => { + it( 'should parse simple query arguments', () => { + const url = 'https://andalouses.example/beach?foo=bar&baz=quux'; + + expect( getQueryArgs( url ) ).toEqual( { + foo: 'bar', + baz: 'quux', + } ); + } ); + + it( 'should accumulate array of values', () => { + const url = + 'https://andalouses.example/beach?foo[]=zero&foo[]=one&foo[]=two'; + + expect( getQueryArgs( url ) ).toEqual( { + foo: [ 'zero', 'one', 'two' ], + } ); + } ); + + it( 'should accumulate keyed array of values', () => { + const url = + 'https://andalouses.example/beach?foo[1]=one&foo[0]=zero&foo[]=two'; + + expect( getQueryArgs( url ) ).toEqual( { + foo: [ 'zero', 'one', 'two' ], + } ); + } ); + + it( 'should accumulate object of values', () => { + const url = + 'https://andalouses.example/beach?foo[zero]=0&foo[one]=1&foo[]=empty'; + + expect( getQueryArgs( url ) ).toEqual( { + foo: { + '': 'empty', + zero: '0', + one: '1', + }, + } ); + } ); + + it( 'normalizes mixed numeric and named keys', () => { + const url = 'https://andalouses.example/beach?foo[0]=0&foo[one]=1'; + + expect( getQueryArgs( url ) ).toEqual( { + foo: { + 0: '0', + one: '1', + }, + } ); + } ); + + it( 'should return empty object for URL without querystring', () => { + const urlWithoutQuerystring = 'https://andalouses.example/beach'; + const urlWithEmptyQuerystring = 'https://andalouses.example/beach?'; + const invalidURL = 'example'; + + expect( getQueryArgs( invalidURL ) ).toEqual( {} ); + expect( getQueryArgs( urlWithoutQuerystring ) ).toEqual( {} ); + expect( getQueryArgs( urlWithEmptyQuerystring ) ).toEqual( {} ); + } ); + + it( 'should gracefully handle empty keys and values', () => { + const url = 'https://andalouses.example/beach?&foo'; + + expect( getQueryArgs( url ) ).toEqual( { + foo: '', + } ); + } ); + + describe( 'reverses buildQueryString', () => { + it( 'unbuilds simple strings', () => { + const data = { + foo: 'bar', + baz: 'boom', + cow: 'milk', + php: 'hypertext processor', + }; + + expect( + getQueryArgs( + 'https://example.com/?foo=bar&baz=boom&cow=milk&php=hypertext%20processor' + ) + ).toEqual( data ); + } ); + + it( 'unbuilds complex data, with stringified values', () => { + const data = { + user: { + name: 'Bob Smith', + age: '47', + sex: 'M', + dob: '5/12/1956', + }, + pastimes: [ 'golf', 'opera', 'poker', 'rap' ], + children: { + bobby: { age: '12', sex: 'M' }, + sally: { age: '8', sex: 'F' }, + }, + }; + + expect( + getQueryArgs( + 'https://example.com/?user%5Bname%5D=Bob%20Smith&user%5Bage%5D=47&user%5Bsex%5D=M&user%5Bdob%5D=5%2F12%2F1956&pastimes%5B0%5D=golf&pastimes%5B1%5D=opera&pastimes%5B2%5D=poker&pastimes%5B3%5D=rap&children%5Bbobby%5D%5Bage%5D=12&children%5Bbobby%5D%5Bsex%5D=M&children%5Bsally%5D%5Bage%5D=8&children%5Bsally%5D%5Bsex%5D=F' + ) + ).toEqual( data ); + } ); + } ); +} ); + describe( 'getQueryArg', () => { it( 'should get the value of an existing query arg', () => { const url = 'https://andalouses.example/beach?foo=bar&bar=baz'; @@ -543,6 +713,7 @@ describe( 'getQueryArg', () => { const url = 'https://andalouses.example/beach?foo=bar&bar=baz#foo'; expect( getQueryArg( url, 'foo' ) ).toEqual( 'bar' ); + expect( getQueryArg( url, 'bar' ) ).toEqual( 'baz' ); } ); } ); @@ -567,6 +738,12 @@ describe( 'hasQueryArg', () => { } ); describe( 'removeQueryArgs', () => { + it( 'should not change URL without a querystring', () => { + const url = 'https://andalouses.example/beach'; + + expect( removeQueryArgs( url, 'baz', 'test' ) ).toEqual( url ); + } ); + it( 'should not change URL not containing query args', () => { const url = 'https://andalouses.example/beach?foo=bar&bar=baz'; diff --git a/packages/viewport/CHANGELOG.md b/packages/viewport/CHANGELOG.md index 37e3c05aab406a..ef834d12a913f5 100644 --- a/packages/viewport/CHANGELOG.md +++ b/packages/viewport/CHANGELOG.md @@ -2,11 +2,15 @@ ## Unreleased +### New Feature + +- Added a store definition `store` for the viewport namespace to use with `@wordpress/data` API ([#26655](https://github.com/WordPress/gutenberg/pull/26655)). + ## 2.1.0 (2019-01-03) ### Improvements -- The initial viewport state is assigned synchronously, rather than on the next process tick. This should have an impact of fewer callbacks made to data subscribers. +- The initial viewport state is assigned synchronously, rather than on the next process tick. This should have an impact of fewer callbacks made to data subscribers. ## 2.0.13 (2018-12-12) @@ -30,4 +34,4 @@ ### Breaking Change -- Change how required built-ins are polyfilled with Babel 7 ([#9171](https://github.com/WordPress/gutenberg/pull/9171)). If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. +- Change how required built-ins are polyfilled with Babel 7 ([#9171](https://github.com/WordPress/gutenberg/pull/9171)). If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. diff --git a/packages/viewport/README.md b/packages/viewport/README.md index 320f46daf8d431..9c4c226c9ab62d 100644 --- a/packages/viewport/README.md +++ b/packages/viewport/README.md @@ -27,19 +27,25 @@ The standard set of breakpoint thresholds is as follows: ### Data Module -The Viewport module registers itself under the `core/viewport` data namespace. +The Viewport module registers itself under the `core/viewport` data namespace and is exposed from the package as `store`. ```js -const isSmall = select( 'core/viewport' ).isViewportMatch( '< medium' ); +import { select } from '@wordpress/data'; +import { store } from '@wordpress/viewport'; + +const isSmall = select( store ).isViewportMatch( '< medium' ); ``` The `isViewportMatch` selector accepts a single string argument `query`. It consists of an optional operator and breakpoint name, separated with a space. The operator can be `<` or `>=`, defaulting to `>=`. ```js -const { isViewportMatch } = select( 'core/viewport' ); +import { select } from '@wordpress/data'; +import { store } from '@wordpress/viewport'; + +const { isViewportMatch } = select( store ); const isSmall = isViewportMatch( '< medium' ); const isWideOrHuge = isViewportMatch( '>= wide' ); -// Equivalent: +// Equivalent: // const isWideOrHuge = isViewportMatch( 'wide' ); ``` @@ -76,6 +82,18 @@ _Returns_ - `Function`: Higher-order component. +<a name="store" href="#store">#</a> **store** + +Store definition for the viewport namespace. + +_Related_ + +- <https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore> + +_Type_ + +- `Object` + <a name="withViewportMatch" href="#withViewportMatch">#</a> **withViewportMatch** Higher-order component creator, creating a new component which renders with diff --git a/packages/viewport/package.json b/packages/viewport/package.json index e428301295c934..fa45f9311e0395 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -22,7 +22,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", "lodash": "^4.17.19" diff --git a/packages/viewport/src/index.js b/packages/viewport/src/index.js index b2faa967569480..8bb1166d60f649 100644 --- a/packages/viewport/src/index.js +++ b/packages/viewport/src/index.js @@ -1,9 +1,9 @@ /** * Internal dependencies */ -import './store'; import addDimensionsEventListener from './listener'; +export { store } from './store'; export { default as ifViewportMatches } from './if-viewport-matches'; export { default as withViewportMatch } from './with-viewport-match'; diff --git a/packages/viewport/src/listener.js b/packages/viewport/src/listener.js index 4d81e15cc3f31d..b87e1b7d3bc2aa 100644 --- a/packages/viewport/src/listener.js +++ b/packages/viewport/src/listener.js @@ -8,6 +8,11 @@ import { reduce, forEach, debounce, mapValues } from 'lodash'; */ import { dispatch } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { store } from './store'; + const addDimensionsEventListener = ( breakpoints, operators ) => { /** * Callback invoked when media query state should be updated. Is invoked a @@ -16,7 +21,7 @@ const addDimensionsEventListener = ( breakpoints, operators ) => { const setIsMatching = debounce( () => { const values = mapValues( queries, ( query ) => query.matches ); - dispatch( 'core/viewport' ).setIsMatching( values ); + dispatch( store ).setIsMatching( values ); }, { leading: true } ); diff --git a/packages/viewport/src/listener.native.js b/packages/viewport/src/listener.native.js index 064ffbb81505dd..ec8bf2203d3e0f 100644 --- a/packages/viewport/src/listener.native.js +++ b/packages/viewport/src/listener.native.js @@ -9,6 +9,11 @@ import { Dimensions } from 'react-native'; */ import { dispatch } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { store } from './store'; + const matchWidth = ( operator, breakpoint ) => { const { width } = Dimensions.get( 'window' ); if ( operator === 'max-width' ) { @@ -34,7 +39,7 @@ const addDimensionsEventListener = ( breakpoints, operators ) => { {} ); - dispatch( 'core/viewport' ).setIsMatching( matches ); + dispatch( store ).setIsMatching( matches ); }; Dimensions.addEventListener( 'change', setIsMatching ); diff --git a/packages/viewport/src/store/index.js b/packages/viewport/src/store/index.js index 88c7cba476165e..29f39dbccc1c16 100644 --- a/packages/viewport/src/store/index.js +++ b/packages/viewport/src/store/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { registerStore } from '@wordpress/data'; +import { createReduxStore, register } from '@wordpress/data'; /** * Internal dependencies @@ -10,8 +10,19 @@ import reducer from './reducer'; import * as actions from './actions'; import * as selectors from './selectors'; -export default registerStore( 'core/viewport', { +const STORE_NAME = 'core/viewport'; + +/** + * Store definition for the viewport namespace. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#createReduxStore + * + * @type {Object} + */ +export const store = createReduxStore( STORE_NAME, { reducer, actions, selectors, } ); + +register( store ); diff --git a/packages/viewport/src/with-viewport-match.native.js b/packages/viewport/src/with-viewport-match.native.js index 983aa22e365f83..6ef490d0ff1d94 100644 --- a/packages/viewport/src/with-viewport-match.native.js +++ b/packages/viewport/src/with-viewport-match.native.js @@ -9,6 +9,11 @@ import { mapValues } from 'lodash'; import { createHigherOrderComponent } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { store } from './store'; + /** * Higher-order component creator, creating a new component which renders with * the given prop names, where the value passed to the underlying component is @@ -36,7 +41,7 @@ const withViewportMatch = ( queries ) => createHigherOrderComponent( withSelect( ( select ) => { return mapValues( queries, ( query ) => { - return select( 'core/viewport' ).isViewportMatch( query ); + return select( store ).isViewportMatch( query ); } ); } ), 'withViewportMatch' diff --git a/packages/wordcount/package.json b/packages/wordcount/package.json index e2b24e71327971..16f777541f348d 100644 --- a/packages/wordcount/package.json +++ b/packages/wordcount/package.json @@ -23,7 +23,7 @@ "react-native": "src/index", "sideEffects": false, "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "lodash": "^4.17.19" }, "publishConfig": { diff --git a/phpunit-watcher.yml.dist b/phpunit-watcher.yml.dist new file mode 100644 index 00000000000000..f2f9da5fbcdbfd --- /dev/null +++ b/phpunit-watcher.yml.dist @@ -0,0 +1,8 @@ +watch: + directories: + - ./lib/ + - ./phpunit/ + +notifications: + passingTests: false + failingTests: false diff --git a/phpunit/bootstrap.php b/phpunit/bootstrap.php index ca8ab6fa0d66e0..96be683a5e2bad 100644 --- a/phpunit/bootstrap.php +++ b/phpunit/bootstrap.php @@ -6,7 +6,7 @@ */ // Require composer dependencies. -require_once dirname( dirname( __FILE__ ) ) . '/vendor/autoload.php'; +require_once dirname( __DIR__ ) . '/vendor/autoload.php'; // If we're running in WP's build directory, ensure that WP knows that, too. if ( 'build' === getenv( 'LOCAL_DIR' ) ) { @@ -24,7 +24,7 @@ // See if we're installed inside an existing WP dev instance. if ( ! $_tests_dir ) { - $_try_tests_dir = dirname( __FILE__ ) . '/../../../../../tests/phpunit'; + $_try_tests_dir = __DIR__ . '/../../../../../tests/phpunit'; if ( file_exists( $_try_tests_dir . '/includes/functions.php' ) ) { $_tests_dir = $_try_tests_dir; } @@ -45,7 +45,7 @@ * Manually load the plugin being tested. */ function _manually_load_plugin() { - require dirname( dirname( __FILE__ ) ) . '/lib/load.php'; + require dirname( __DIR__ ) . '/lib/load.php'; } tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' ); @@ -73,6 +73,7 @@ function fail_if_died( $message ) { $GLOBALS['wp_tests_options'] = array( 'gutenberg-experiments' => array( 'gutenberg-widget-experiments' => '1', + 'gutenberg-full-site-editing' => 1, ), ); diff --git a/phpunit/class-edit-site-export-test.php b/phpunit/class-edit-site-export-test.php new file mode 100644 index 00000000000000..5b7ddbe7f77856 --- /dev/null +++ b/phpunit/class-edit-site-export-test.php @@ -0,0 +1,29 @@ +<?php + +/** + * Test `gutenberg_edit_site_export` and its helper functions. + * + * @package Gutenberg + */ + +class Edit_Site_Export_Test extends WP_UnitTestCase { + /** + * Verify that post ids are stripped from template part blocks during the export. + * + * This is needed so that the exported template is loaded from the theme. + */ + public function test_post_ids_are_stripped_from_template_parts() { + $post = self::factory()->post->create_and_get( + array( + 'post_content' => '<!-- wp:template-part {"postId":123,"slug":"header","theme":"gutenberg-tests"} /-->', + 'post_title' => 'Archive', + 'post_type' => 'wp_template', + ) + ); + + $this->assertSame( + '<!-- wp:template-part {"slug":"header","theme":"gutenberg-tests"} /-->', + gutenberg_strip_post_ids_from_template_part_blocks( $post->post_content ) + ); + } +} diff --git a/phpunit/class-gutenberg-utils-test.php b/phpunit/class-gutenberg-utils-test.php new file mode 100644 index 00000000000000..bac88678ea8263 --- /dev/null +++ b/phpunit/class-gutenberg-utils-test.php @@ -0,0 +1,130 @@ +<?php +/** + * Test Gutenberg Utils. + * + * @package Gutenberg + */ + +/** + * Test WP_Theme_JSON class. + * + * @package Gutenberg + */ +class Gutenberg_Utils_Test extends WP_UnitTestCase { + /** + * Test gutenberg_experimental_set() with simple non subtree path. + */ + public function test_simple_not_subtree_set() { + $test_array = array(); + gutenberg_experimental_set( $test_array, array( 'a' ), 1 ); + $this->assertSame( + $test_array, + array( 'a' => 1 ) + ); + + $test_array = array( 'a' => 2 ); + gutenberg_experimental_set( $test_array, array( 'a' ), 3 ); + $this->assertSame( + $test_array, + array( 'a' => 3 ) + ); + + $test_array = array( 'b' => 1 ); + gutenberg_experimental_set( $test_array, array( 'a' ), 3 ); + $this->assertSame( + $test_array, + array( + 'b' => 1, + 'a' => 3, + ) + ); + } + + /** + * Test gutenberg_experimental_set() with subtree paths. + */ + public function test_subtree_set() { + $test_array = array(); + gutenberg_experimental_set( $test_array, array( 'a', 'b', 'c' ), 1 ); + $this->assertSame( + $test_array, + array( 'a' => array( 'b' => array( 'c' => 1 ) ) ) + ); + + $test_array = array( 'b' => 3 ); + gutenberg_experimental_set( $test_array, array( 'a', 'b', 'c' ), 1 ); + $this->assertSame( + $test_array, + array( + 'b' => 3, + 'a' => array( 'b' => array( 'c' => 1 ) ), + ) + ); + + $test_array = array( + 'b' => 3, + 'a' => 1, + ); + gutenberg_experimental_set( $test_array, array( 'a', 'b', 'c' ), 1 ); + $this->assertSame( + $test_array, + array( + 'b' => 3, + 'a' => array( 'b' => array( 'c' => 1 ) ), + ) + ); + + $test_array = array( + 'b' => 3, + 'a' => array(), + ); + gutenberg_experimental_set( $test_array, array( 'a', 'b', 'c' ), 1 ); + $this->assertSame( + $test_array, + array( + 'b' => 3, + 'a' => array( 'b' => array( 'c' => 1 ) ), + ) + ); + } + + /** + * Test gutenberg_experimental_set() with invalid parameters. + */ + public function test_invalid_parameters_set() { + $test = 3; + gutenberg_experimental_set( $test, array( 'a' ), 1 ); + $this->assertSame( + $test, + 3 + ); + + $test_array = array( 'a' => 2 ); + gutenberg_experimental_set( $test_array, 'a', 3 ); + $this->assertSame( + $test_array, + array( 'a' => 2 ) + ); + + $test_array = array( 'a' => 2 ); + gutenberg_experimental_set( $test_array, null, 3 ); + $this->assertSame( + $test_array, + array( 'a' => 2 ) + ); + + $test_array = array( 'a' => 2 ); + gutenberg_experimental_set( $test_array, array(), 3 ); + $this->assertSame( + $test_array, + array( 'a' => 2 ) + ); + + $test_array = array( 'a' => 2 ); + gutenberg_experimental_set( $test_array, array( 'a', array() ), 3 ); + $this->assertSame( + $test_array, + array( 'a' => 2 ) + ); + } +} diff --git a/phpunit/class-rest-nav-menu-items-controller-test.php b/phpunit/class-rest-nav-menu-items-controller-test.php index 69593227a32acf..34dd004e162fe4 100644 --- a/phpunit/class-rest-nav-menu-items-controller-test.php +++ b/phpunit/class-rest-nav-menu-items-controller-test.php @@ -842,7 +842,7 @@ protected function check_menu_item_data( $post, $data, $context, $links ) { $this->assertEquals( $links['about'][0]['href'], rest_url( 'wp/v2/types/' . self::POST_TYPE ) ); $num = 0; - foreach ( $taxonomies as $key => $taxonomy ) { + foreach ( $taxonomies as $taxonomy ) { $this->assertEquals( $taxonomy->name, $links['https://api.w.org/term'][ $num ]['attributes']['taxonomy'] ); $this->assertEquals( add_query_arg( 'post', $data['id'], rest_url( 'wp/v2/' . $taxonomy->rest_base ) ), $links['https://api.w.org/term'][ $num ]['href'] ); $num ++; diff --git a/phpunit/class-wp-theme-json-legacy-settings-test.php b/phpunit/class-wp-theme-json-legacy-settings-test.php new file mode 100644 index 00000000000000..addadeceeccb06 --- /dev/null +++ b/phpunit/class-wp-theme-json-legacy-settings-test.php @@ -0,0 +1,193 @@ +<?php + +/** + * Test that legacy settings are properly + * reorganized into the theme.json structure. + * + * @package Gutenberg + */ + +class Theme_JSON_Legacy_Settings_Test extends WP_UnitTestCase { + + function get_editor_settings_no_theme_support() { + return array( + '__unstableEnableFullSiteEditingBlocks' => false, + 'disableCustomColors' => false, + 'disableCustomFontSizes' => false, + 'disableCustomGradients' => false, + 'enableCustomLineHeight' => false, + 'enableCustomUnits' => false, + 'imageSizes' => array( + array( + 'slug' => 'thumbnail', + 'name' => 'Thumbnail', + ), + array( + 'slug' => 'medium', + 'name' => 'Medium', + ), + array( + 'slug' => 'large', + 'name' => 'Large', + ), + array( + 'slug' => 'full', + 'name' => 'Full Size', + ), + ), + 'isRTL' => false, + 'maxUploadFileSize' => 123, + ); + } + + function test_legacy_settings_blank() { + $input = array(); + $expected = array( + 'global' => array( + 'settings' => array(), + ), + ); + + $actual = gutenberg_experimental_global_styles_get_theme_support_settings( $input ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + function test_legacy_settings_no_theme_support() { + $input = $this->get_editor_settings_no_theme_support(); + $expected = array( + 'global' => array( + 'settings' => array( + 'color' => array( + 'custom' => true, + 'customGradient' => true, + ), + 'spacing' => array( + 'units' => false, + ), + 'typography' => array( + 'customFontSize' => true, + 'customLineHeight' => false, + ), + ), + ), + ); + + $actual = gutenberg_experimental_global_styles_get_theme_support_settings( $input ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + function test_legacy_settings_custom_units_can_be_disabled() { + add_theme_support( 'custom-units', array() ); + $input = gutenberg_get_common_block_editor_settings(); + + $expected = array( + 'units' => array( array() ), + ); + + $actual = gutenberg_experimental_global_styles_get_theme_support_settings( $input ); + + $this->assertEqualSetsWithIndex( $expected, $actual['global']['settings']['spacing'] ); + } + + function test_legacy_settings_custom_units_can_be_enabled() { + add_theme_support( 'custom-units' ); + $input = gutenberg_get_common_block_editor_settings(); + + $expected = array( + 'units' => array( 'px', 'em', 'rem', 'vh', 'vw' ), + ); + + $actual = gutenberg_experimental_global_styles_get_theme_support_settings( $input ); + + $this->assertEqualSetsWithIndex( $expected, $actual['global']['settings']['spacing'] ); + } + + function test_legacy_settings_custom_units_can_be_filtered() { + add_theme_support( 'custom-units', 'rem', 'em' ); + $input = gutenberg_get_common_block_editor_settings(); + + $expected = array( + 'units' => array( 'rem', 'em' ), + ); + + $actual = gutenberg_experimental_global_styles_get_theme_support_settings( $input ); + + $this->assertEqualSetsWithIndex( $expected, $actual['global']['settings']['spacing'] ); + } + + function test_legacy_settings_filled() { + $input = array( + 'disableCustomColors' => true, + 'disableCustomGradients' => true, + 'disableCustomFontSizes' => true, + 'enableCustomLineHeight' => true, + 'enableCustomUnits' => true, + 'colors' => array( + array( + 'slug' => 'color-slug', + 'name' => 'Color Name', + 'color' => 'colorvalue', + ), + ), + 'gradients' => array( + array( + 'slug' => 'gradient-slug', + 'name' => 'Gradient Name', + 'gradient' => 'gradientvalue', + ), + ), + 'fontSizes' => array( + array( + 'slug' => 'size-slug', + 'name' => 'Size Name', + 'size' => 'sizevalue', + ), + ), + ); + + $expected = array( + 'global' => array( + 'settings' => array( + 'color' => array( + 'custom' => false, + 'customGradient' => false, + 'gradients' => array( + array( + 'slug' => 'gradient-slug', + 'name' => 'Gradient Name', + 'gradient' => 'gradientvalue', + ), + ), + 'palette' => array( + array( + 'slug' => 'color-slug', + 'name' => 'Color Name', + 'color' => 'colorvalue', + ), + ), + ), + 'spacing' => array( + 'units' => array( 'px', 'em', 'rem', 'vh', 'vw' ), + ), + 'typography' => array( + 'customFontSize' => false, + 'customLineHeight' => true, + 'fontSizes' => array( + array( + 'slug' => 'size-slug', + 'name' => 'Size Name', + 'size' => 'sizevalue', + ), + ), + ), + ), + ), + ); + + $actual = gutenberg_experimental_global_styles_get_theme_support_settings( $input ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } +} diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php new file mode 100644 index 00000000000000..8b5193a7f3a3ee --- /dev/null +++ b/phpunit/class-wp-theme-json-test.php @@ -0,0 +1,404 @@ +<?php + +/** + * Test WP_Theme_JSON class. + * + * @package Gutenberg + */ + +class WP_Theme_JSON_Test extends WP_UnitTestCase { + + function test_contexts_not_valid_are_skipped() { + $theme_json = new WP_Theme_JSON( + array( + 'global' => array( + 'settings' => array( + 'color' => array( + 'custom' => 'false', + ), + ), + ), + 'core/invalid' => array( + 'settings' => array( + 'color' => array( + 'custom' => 'false', + ), + ), + ), + ) + ); + $result = $theme_json->get_raw_data(); + + $expected = array( + 'global' => array( + 'settings' => array( + 'color' => array( + 'custom' => 'false', + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + function test_properties_not_valid_are_skipped() { + $theme_json = new WP_Theme_JSON( + array( + 'global' => array( + 'invalidKey' => 'invalid value', + 'settings' => array( + 'color' => array( + 'custom' => 'false', + 'invalidKey' => 'invalid value', + ), + 'invalidSection' => array( + 'invalidKey' => 'invalid value', + ), + ), + 'styles' => array( + 'typography' => array( + 'fontSize' => '12', + 'invalidProperty' => 'invalid value', + ), + 'invalidSection' => array( + 'invalidProperty' => 'invalid value', + ), + ), + ), + ) + ); + $result = $theme_json->get_raw_data(); + + $expected = array( + 'global' => array( + 'settings' => array( + 'color' => array( + 'custom' => 'false', + ), + ), + 'styles' => array( + 'typography' => array( + 'fontSize' => '12', + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + function test_get_settings() { + // See schema at WP_Theme_JSON::SCHEMA. + $theme_json = new WP_Theme_JSON( + array( + 'global' => array( + 'settings' => array( + 'color' => array( + 'link' => 'value', + ), + 'custom' => 'value', + 'typography' => 'value', + 'misc' => 'value', + ), + 'styles' => array( + 'color' => 'value', + 'misc' => 'value', + ), + 'misc' => 'value', + ), + ) + ); + + $result = $theme_json->get_settings(); + + $this->assertArrayHasKey( 'global', $result ); + $this->assertCount( 1, $result ); + + $this->assertArrayHasKey( 'color', $result['global'] ); + $this->assertArrayHasKey( 'custom', $result['global'] ); + $this->assertCount( 2, $result['global'] ); + } + + function test_get_stylesheet() { + // See schema at WP_Theme_JSON::SCHEMA. + $theme_json = new WP_Theme_JSON( + array( + 'global' => array( + 'settings' => array( + 'color' => array( + 'text' => 'value', + 'palette' => array( + array( + 'slug' => 'grey', + 'color' => 'grey', + ), + ), + ), + 'typography' => array( + 'fontFamilies' => array( + array( + 'slug' => 'small', + 'fontFamily' => '14px', + ), + array( + 'slug' => 'big', + 'fontFamily' => '41px', + ), + ), + ), + 'misc' => 'value', + ), + 'styles' => array( + 'color' => array( + 'link' => '#111', + 'text' => 'var:preset|color|grey', + ), + 'misc' => 'value', + ), + 'misc' => 'value', + ), + ) + ); + + $this->assertEquals( + $theme_json->get_stylesheet(), + ':root{--wp--preset--color--grey: grey;--wp--preset--font-family--small: 14px;--wp--preset--font-family--big: 41px;}:root{--wp--style--color--link: #111;color: var(--wp--preset--color--grey);}.has-grey-color{color: grey;}.has-grey-background-color{background-color: grey;}' + ); + $this->assertEquals( + $theme_json->get_stylesheet( 'block_styles' ), + ':root{--wp--style--color--link: #111;color: var(--wp--preset--color--grey);}.has-grey-color{color: grey;}.has-grey-background-color{background-color: grey;}' + ); + $this->assertEquals( + $theme_json->get_stylesheet( 'css_variables' ), + ':root{--wp--preset--color--grey: grey;--wp--preset--font-family--small: 14px;--wp--preset--font-family--big: 41px;}' + ); + } + + public function test_merge_incoming_data() { + $initial = array( + 'global' => array( + 'settings' => array( + 'color' => array( + 'custom' => 'false', + 'palette' => array( + array( + 'slug' => 'red', + 'color' => 'red', + ), + array( + 'slug' => 'blue', + 'color' => 'blue', + ), + ), + ), + ), + 'styles' => array( + 'typography' => array( + 'fontSize' => '12', + ), + ), + ), + 'core/paragraph' => array( + 'settings' => array( + 'color' => array( + 'custom' => 'false', + ), + ), + ), + ); + + $add_new_context = array( + 'core/list' => array( + 'settings' => array( + 'color' => array( + 'custom' => 'false', + ), + ), + 'styles' => array( + 'typography' => array( + 'fontSize' => '12', + ), + 'color' => array( + 'link' => 'pink', + 'background' => 'brown', + ), + ), + ), + ); + + $add_key_in_settings = array( + 'global' => array( + 'settings' => array( + 'color' => array( + 'customGradient' => 'true', + ), + ), + ), + ); + + $update_key_in_settings = array( + 'global' => array( + 'settings' => array( + 'color' => array( + 'custom' => 'true', + ), + ), + ), + ); + + $add_styles = array( + 'core/paragraph' => array( + 'styles' => array( + 'typography' => array( + 'fontSize' => '12', + ), + 'color' => array( + 'link' => 'pink', + ), + ), + ), + ); + + $add_key_in_styles = array( + 'core/paragraph' => array( + 'styles' => array( + 'typography' => array( + 'lineHeight' => '12', + ), + ), + ), + ); + + $add_invalid_context = array( + 'core/para' => array( + 'styles' => array( + 'typography' => array( + 'lineHeight' => '12', + ), + ), + ), + ); + + $update_presets = array( + 'global' => array( + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'color', + 'color' => 'color', + ), + ), + 'gradients' => array( + array( + 'slug' => 'gradient', + 'gradient' => 'gradient', + ), + ), + ), + 'typography' => array( + 'fontSizes' => array( + array( + 'slug' => 'fontSize', + 'size' => 'fontSize', + ), + ), + 'fontFamilies' => array( + array( + 'slug' => 'fontFamily', + 'fontFamily' => 'fontFamily', + ), + ), + ), + ), + ), + ); + + $expected = array( + 'global' => array( + 'settings' => array( + 'color' => array( + 'custom' => 'true', + 'customGradient' => 'true', + 'palette' => array( + array( + 'slug' => 'color', + 'color' => 'color', + ), + ), + 'gradients' => array( + array( + 'slug' => 'gradient', + 'gradient' => 'gradient', + ), + ), + ), + 'typography' => array( + 'fontSizes' => array( + array( + 'slug' => 'fontSize', + 'size' => 'fontSize', + ), + ), + 'fontFamilies' => array( + array( + 'slug' => 'fontFamily', + 'fontFamily' => 'fontFamily', + ), + ), + ), + ), + 'styles' => array( + 'typography' => array( + 'fontSize' => '12', + ), + ), + ), + 'core/paragraph' => array( + 'settings' => array( + 'color' => array( + 'custom' => 'false', + ), + ), + 'styles' => array( + 'typography' => array( + 'fontSize' => '12', + 'lineHeight' => '12', + ), + 'color' => array( + 'link' => 'pink', + ), + ), + ), + 'core/list' => array( + 'settings' => array( + 'color' => array( + 'custom' => 'false', + ), + ), + 'styles' => array( + 'typography' => array( + 'fontSize' => '12', + ), + 'color' => array( + 'link' => 'pink', + 'background' => 'brown', + ), + ), + ), + ); + + $theme_json = new WP_Theme_JSON( $initial ); + $theme_json->merge( new WP_Theme_JSON( $add_new_context ) ); + $theme_json->merge( new WP_Theme_JSON( $add_key_in_settings ) ); + $theme_json->merge( new WP_Theme_JSON( $update_key_in_settings ) ); + $theme_json->merge( new WP_Theme_JSON( $add_styles ) ); + $theme_json->merge( new WP_Theme_JSON( $add_key_in_styles ) ); + $theme_json->merge( new WP_Theme_JSON( $add_invalid_context ) ); + $theme_json->merge( new WP_Theme_JSON( $update_presets ) ); + $result = $theme_json->get_raw_data(); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } +} diff --git a/readme.txt b/readme.txt index c2b167005ca632..691830be65932a 100644 --- a/readme.txt +++ b/readme.txt @@ -57,4 +57,4 @@ View <a href="https://developer.wordpress.org/block-editor/principles/versions-i == Changelog == -To read the changelog for Gutenberg 9.2.0-rc.1, please navigate to the <a href="https://github.com/WordPress/gutenberg/releases/tag/v9.2.0-rc.1">release page</a>. +To read the changelog for Gutenberg 9.6.0-rc.1, please navigate to the <a href="https://github.com/WordPress/gutenberg/releases/tag/v9.6.0-rc.1">release page</a>. diff --git a/storybook/stories/playground/editor-styles.scss b/storybook/stories/playground/editor-styles.scss index d983f820afc65d..94665ce8c3427d 100644 --- a/storybook/stories/playground/editor-styles.scss +++ b/storybook/stories/playground/editor-styles.scss @@ -44,7 +44,7 @@ } .wp-block[data-align="wide"], .wp-block.alignwide { - max-width: 1100px; + max-width: $content-width; } .wp-block[data-align="full"], .wp-block.alignfull { diff --git a/storybook/stories/playground/style.scss b/storybook/stories/playground/style.scss index 187fc7681b96ef..ee7da78f7ff0b7 100644 --- a/storybook/stories/playground/style.scss +++ b/storybook/stories/playground/style.scss @@ -22,10 +22,6 @@ iframe { width: 100%; } - - .components-navigate-regions { - height: 100%; - } } .playground__sidebar { diff --git a/test/integration/full-content/full-content.test.js b/test/integration/full-content/full-content.test.js index 86f5f5c10e271e..5e896387b56621 100644 --- a/test/integration/full-content/full-content.test.js +++ b/test/integration/full-content/full-content.test.js @@ -65,14 +65,11 @@ describe( 'full post content fixture', () => { } ) ); unstable__bootstrapServerSideBlockDefinitions( blockDefinitions ); - const settings = { - __experimentalEnableFullSiteEditing: true, - }; // Load all hooks that modify blocks require( '../../../packages/editor/src/hooks' ); registerCoreBlocks(); if ( process.env.GUTENBERG_PHASE === 2 ) { - __experimentalRegisterExperimentalCoreBlocks( settings ); + __experimentalRegisterExperimentalCoreBlocks( true ); } } ); diff --git a/test/native/__mocks__/styleMock.js b/test/native/__mocks__/styleMock.js index 55fa4dd0a954c9..f01d424bb9d162 100644 --- a/test/native/__mocks__/styleMock.js +++ b/test/native/__mocks__/styleMock.js @@ -93,4 +93,17 @@ module.exports = { defaultBlock: { marginTop: 16, }, + scrollableContent: { + paddingBottom: 20, + }, + buttonText: { + color: 'white', + }, + placeholderTextColor: { + color: 'white', + }, + defaultButton: { + paddingLeft: 10, + paddingRight: 10, + }, }; diff --git a/test/native/setup.js b/test/native/setup.js index 5325be4e1fe869..d6125cc22fe688 100644 --- a/test/native/setup.js +++ b/test/native/setup.js @@ -30,6 +30,7 @@ jest.mock( '@wordpress/react-native-bridge', () => { editorDidMount: jest.fn(), editorDidAutosave: jest.fn(), subscribeMediaUpload: jest.fn(), + subscribeMediaSave: jest.fn(), getOtherMediaOptions: jest.fn(), requestMediaPicker: jest.fn(), requestUnsupportedBlockFallback: jest.fn(), diff --git a/tsconfig.base.json b/tsconfig.base.json index c9c8269e1f6efb..495796402bdf85 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -33,14 +33,13 @@ "types": [] }, "exclude": [ - "**/benchmark", - "**/test/**", - "**/build/**", - "**/build-*/**", "**/*.android.js", "**/*.ios.js", "**/*.native.js", - "packages/**/react-native-*/**", - "packages/**/stories" + "**/benchmark", + "packages/*/build-*/**", + "packages/*/build/**", + "**/test/**", + "packages/**/react-native-*/**" ] } diff --git a/tsconfig.json b/tsconfig.json index 57392eebc289f9..003daf1af802cc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,12 +6,13 @@ { "path": "packages/blob" }, { "path": "packages/block-editor" }, { "path": "packages/components" }, + { "path": "packages/deprecated" }, { "path": "packages/element" }, - { "path": "packages/dependency-extraction-webpack-plugin" }, { "path": "packages/dom-ready" }, { "path": "packages/escape-html" }, { "path": "packages/eslint-plugin" }, { "path": "packages/html-entities" }, + { "path": "packages/hooks" }, { "path": "packages/i18n" }, { "path": "packages/icons" }, { "path": "packages/is-shallow-equal" }, diff --git a/typings/gutenberg-env/index.d.ts b/typings/gutenberg-env/index.d.ts index 387d4239a21c6a..7fe6d587446f59 100644 --- a/typings/gutenberg-env/index.d.ts +++ b/typings/gutenberg-env/index.d.ts @@ -1,7 +1,7 @@ interface Environment { - NODE_ENV?: unknown; + NODE_ENV: unknown; } interface Process { - env?: Environment; + env: Environment; } declare var process: Process; diff --git a/webpack.config.js b/webpack.config.js index 8fe68b41cb6a68..de8be6fab09e98 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -41,6 +41,29 @@ const gutenbergPackages = Object.keys( dependencies ) ) .map( ( packageName ) => packageName.replace( WORDPRESS_NAMESPACE, '' ) ); +const stylesTransform = ( content ) => { + if ( mode === 'production' ) { + return postcss( [ + require( 'cssnano' )( { + preset: [ + 'default', + { + discardComments: { + removeAll: true, + }, + }, + ], + } ), + ] ) + .process( content, { + from: 'src/app.css', + to: 'dest/app.css', + } ) + .then( ( result ) => result.css ); + } + return content; +}; + module.exports = { optimization: { // Only concatenate modules in production, when not analyzing bundles. @@ -55,6 +78,9 @@ module.exports = { output: { comments: /translators:/i, }, + compress: { + passes: 2, + }, }, extractComments: false, } ), @@ -71,7 +97,7 @@ module.exports = { filename: './build/[basename]/index.js', path: __dirname, library: [ 'wp', '[name]' ], - libraryTarget: 'this', + libraryTarget: 'window', }, module: { rules: compact( [ @@ -137,30 +163,43 @@ module.exports = { from: `./packages/${ packageName }/build-style/*.css`, to: `./build/${ packageName }/`, flatten: true, - transform: ( content ) => { - if ( mode === 'production' ) { - return postcss( [ - require( 'cssnano' )( { - preset: [ - 'default', - { - discardComments: { - removeAll: true, - }, - }, - ], - } ), - ] ) - .process( content, { - from: 'src/app.css', - to: 'dest/app.css', - } ) - .then( ( result ) => result.css ); - } - return content; - }, + transform: stylesTransform, } ) ) ), + new CopyWebpackPlugin( [ + { + from: './packages/block-library/build-style/*/style.css', + test: new RegExp( + `([\\w-]+)${ escapeRegExp( sep ) }style\\.css$` + ), + to: 'build/block-library/blocks/[1]/style.css', + transform: stylesTransform, + }, + { + from: './packages/block-library/build-style/*/style-rtl.css', + test: new RegExp( + `([\\w-]+)${ escapeRegExp( sep ) }style-rtl\\.css$` + ), + to: 'build/block-library/blocks/[1]/style-rtl.css', + transform: stylesTransform, + }, + { + from: './packages/block-library/build-style/*/editor.css', + test: new RegExp( + `([\\w-]+)${ escapeRegExp( sep ) }editor\\.css$` + ), + to: 'build/block-library/blocks/[1]/editor.css', + transform: stylesTransform, + }, + { + from: './packages/block-library/build-style/*/editor-rtl.css', + test: new RegExp( + `([\\w-]+)${ escapeRegExp( sep ) }editor-rtl\\.css$` + ), + to: 'build/block-library/blocks/[1]/editor-rtl.css', + transform: stylesTransform, + }, + ] ), new CopyWebpackPlugin( Object.entries( { './packages/block-library/src/': 'build/block-library/blocks/',