diff --git a/.eslintrc.js b/.eslintrc.js index 9933b831e4725a..eb5b2c6dccf201 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -44,123 +44,7 @@ const restrictedImports = [ }, { name: 'lodash', - importNames: [ - 'camelCase', - 'capitalize', - 'castArray', - 'chunk', - 'clamp', - 'clone', - 'cloneDeep', - 'compact', - 'concat', - 'countBy', - 'debounce', - 'deburr', - 'defaults', - 'defaultTo', - 'delay', - 'difference', - 'differenceWith', - 'dropRight', - 'each', - 'escape', - 'escapeRegExp', - 'every', - 'extend', - 'filter', - 'find', - 'findIndex', - 'findKey', - 'findLast', - 'first', - 'flatMap', - 'flatten', - 'flattenDeep', - 'flow', - 'flowRight', - 'forEach', - 'fromPairs', - 'groupBy', - 'has', - 'identity', - 'includes', - 'invoke', - 'isArray', - 'isBoolean', - 'isEmpty', - 'isEqual', - 'isFinite', - 'isFunction', - 'isMatch', - 'isNil', - 'isNumber', - 'isObject', - 'isObjectLike', - 'isPlainObject', - 'isString', - 'isUndefined', - 'kebabCase', - 'keyBy', - 'keys', - 'last', - 'lowerCase', - 'map', - 'mapKeys', - 'mapValues', - 'maxBy', - 'memoize', - 'merge', - 'mergeWith', - 'negate', - 'noop', - 'nth', - 'omit', - 'omitBy', - 'once', - 'orderby', - 'overEvery', - 'partial', - 'partialRight', - 'pick', - 'pickBy', - 'random', - 'reduce', - 'reject', - 'repeat', - 'reverse', - 'setWith', - 'size', - 'snakeCase', - 'some', - 'sortBy', - 'startCase', - 'startsWith', - 'stubFalse', - 'stubTrue', - 'sum', - 'sumBy', - 'take', - 'throttle', - 'times', - 'toString', - 'trim', - 'truncate', - 'unescape', - 'unionBy', - 'uniq', - 'uniqBy', - 'uniqueId', - 'uniqWith', - 'upperFirst', - 'values', - 'without', - 'words', - 'xor', - 'zip', - ], - message: - 'This Lodash method is not recommended. Please use native functionality instead. If using `memoize`, please use `memize` instead.', + message: 'Please use native functionality instead.', }, { name: 'reakit', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7ba9e884259ea4..4008efa1b4d4e1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -88,6 +88,7 @@ /packages/compose @ajitbohra /packages/element @ajitbohra /packages/notices @ajitbohra +/packages/nux @ajitbohra @peterwilsoncc /packages/viewport @ajitbohra /packages/base-styles /packages/icons diff --git a/.github/workflows/build-plugin-zip.yml b/.github/workflows/build-plugin-zip.yml index 9052f1689c9216..f57ccd21e361e8 100644 --- a/.github/workflows/build-plugin-zip.yml +++ b/.github/workflows/build-plugin-zip.yml @@ -56,7 +56,7 @@ jobs: github.event.inputs.version == 'rc' || github.event.inputs.version == 'stable' ) || ( - endsWith( github.ref, needs.compute-stable-branches.outputs.current_stable_branch ) && + startsWith( github.ref, 'refs/heads/release/' ) && github.event.inputs.version == 'stable' ) ) diff --git a/.github/workflows/php-changes-detection.yml b/.github/workflows/php-changes-detection.yml new file mode 100644 index 00000000000000..be1e686483f5b3 --- /dev/null +++ b/.github/workflows/php-changes-detection.yml @@ -0,0 +1,100 @@ +name: OPTIONAL - Confirm if PHP changes require backporting to WordPress Core + +on: + pull_request: + types: [opened, synchronize] +jobs: + detect_php_changes: + name: Detect PHP changes + runs-on: ubuntu-latest + if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} + steps: + - name: Check out code + uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 + with: + fetch-depth: 0 + + - name: Get changed PHP files + id: changed-files-php + uses: tj-actions/changed-files@v37 + with: + files: | + *.{php} + lib/** + phpunit/** + + - name: List all changed files + if: steps.changed-files-php.outputs.any_changed == 'true' + id: list-changed-php-files + run: | + echo "Changed files:" + formatted_change_list="" + for file in ${{ steps.changed-files-php.outputs.all_changed_files }}; do + echo "$file was changed" + formatted_change_list+="
:grey_question: $file" + done + formatted_change_list+="
" + echo "formatted_change_list=$formatted_change_list" >> $GITHUB_OUTPUT + + - name: Find Comment + uses: peter-evans/find-comment@v2 + id: find-comment + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: '' + + - name: Create comment + if: steps.find-comment.outputs.comment-id == '' && steps.changed-files-php.outputs.any_changed == 'true' + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + + This pull request has changed or added PHP files. Please confirm whether these changes need to be synced to WordPress Core, and therefore featured in the next release of WordPress. + + If so, it is recommended to create a [new Trac ticket](https://core.trac.wordpress.org/newticket) and submit a pull request to the [WordPress Core Github repository](https://github.com/WordPress/wordpress-develop) soon after this pull request is merged. + + If you're unsure, you can always ask for help in the #core-editor channel in [WordPress Slack](https://make.wordpress.org/chat/). + + Thank you! :heart: + +
+ View changed files + ${{ steps.list-changed-php-files.outputs.formatted_change_list }} +
+ + - name: Update comment + if: steps.find-comment.outputs.comment-id != '' && steps.changed-files-php.outputs.any_changed == 'true' + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + edit-mode: replace + body: | + + This pull request has changed or added PHP files. Please confirm whether these changes need to be synced to WordPress Core, and therefore featured in the next release of WordPress. + + If so, it is recommended to create a [new Trac ticket](https://core.trac.wordpress.org/newticket) and submit a pull request to the [WordPress Core Github repository](https://github.com/WordPress/wordpress-develop) soon after this pull request is merged. + + If you're unsure, you can always ask for help in the #core-editor channel in [WordPress Slack](https://make.wordpress.org/chat/). + + Thank you! :heart: + +
+ View changed files + ${{ steps.list-changed-php-files.outputs.formatted_change_list }} +
+ + - name: Update comment + if: steps.find-comment.outputs.comment-id != '' && steps.changed-files-php.outputs.any_changed != 'true' + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + edit-mode: replace + body: | + + This pull request changed or added PHP files in previous commits, but none have been detected in the latest commit. + + Thank you! :heart: diff --git a/.github/workflows/stale-issue-gardening.yml b/.github/workflows/stale-issue-gardening.yml index 078108a182850e..da823198ed806f 100644 --- a/.github/workflows/stale-issue-gardening.yml +++ b/.github/workflows/stale-issue-gardening.yml @@ -38,7 +38,7 @@ jobs: days-before-stale: 180 days-before-close: -1 remove-stale-when-updated: false - stale-issue-label: 'Needs Testing' + stale-issue-label: 'Needs check-in' steps: - name: Update issues diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 9c93b6b93d98dc..09494f0d514ad4 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -85,7 +85,6 @@ jobs: fail-fast: true matrix: php: - - '5.6' - '7.0' - '7.1' - '7.2' @@ -297,4 +296,4 @@ jobs: run: npx lerna run build - name: Running the tests - run: npm run native test -- --ci --maxWorkers=2 --cacheDirectory="$HOME/.jest-cache" + run: npm run test:native -- --ci --maxWorkers=2 --cacheDirectory="$HOME/.jest-cache" diff --git a/.github/workflows/upload-release-to-plugin-repo.yml b/.github/workflows/upload-release-to-plugin-repo.yml index 392cf912e8db54..521fd3a786f15e 100644 --- a/.github/workflows/upload-release-to-plugin-repo.yml +++ b/.github/workflows/upload-release-to-plugin-repo.yml @@ -5,10 +5,66 @@ on: types: [published] jobs: + compute-should-update-trunk: + name: Decide if trunk or tag + runs-on: ubuntu-latest + # Skip this job if the release is a release candidate. This will in turn skip + # the upload jobs, which are only relevant for non-RC releases. + # We first check if the release is a prerelease, and then if the ref contains + # the string "rc". The latter is fallback in case the deployer accidentally + # unchecks the "This is a pre-release" checkbox in the release UI. + if: | + !github.event.release.prerelease && !contains(github.ref, 'rc') + + outputs: + should_update_trunk: ${{ steps.compute_should_update_trunk.outputs.should_update_trunk }} + + steps: + - name: Fetch latest version in the WP core repo + id: compute_latest_version_in_core_repo + run: | + latest_version_in_core_repo=$(curl -s 'https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&request\[slug\]=gutenberg' | jq -r '.version') + echo "Latest Core Repo version: $latest_version_in_core_repo" + echo "version=$latest_version_in_core_repo" >> $GITHUB_OUTPUT + + - name: Decide if it is a trunk or tag update + id: compute_should_update_trunk + env: + GITHUB_REF: ${{ github.ref }} + run: | + latestPublishedVersion=$(echo "$GITHUB_REF" | sed -E 's/refs\/tags\/(v?)([0-9.]+)/\2/') + latestVersionInCoreRepo="${{ steps.compute_latest_version_in_core_repo.outputs.version }}" + + # Determines if the first version string is greater than the second version string. + # + # Params: + # $1 - The first version string to compare, which may have an optional leading "v". + # $2 - The second version string to compare, which may have an optional leading "v". + # + # Return values: + # 0 - The first version string is greater than the second version string. + # 1 - The first version string is less than or equal to the second version string. + is_first_version_greater_than_second() { + v1=${1#v} + v2=${2#v} + dpkg --compare-versions "$v1" gt "$v2" + return $? + } + + # Only update trunk *if* the published release's version in Github is GREATER + # than the version currently published in the WP plugins repo. If not, then it + # will upload it as a new tag. + shouldUpdateTrunk=false + if is_first_version_greater_than_second "$latestPublishedVersion" "$latestVersionInCoreRepo"; then + shouldUpdateTrunk=true + fi + + echo "Should update trunk: $shouldUpdateTrunk" + echo "should_update_trunk=$shouldUpdateTrunk" >> $GITHUB_OUTPUT + get-release-branch: name: Get release branch name runs-on: ubuntu-latest - if: github.event.release.assets[0] outputs: release_branch: ${{ steps.get_release_branch.outputs.release_branch }} @@ -25,7 +81,8 @@ jobs: update-changelog: name: Update Changelog on ${{ matrix.branch }} branch runs-on: ubuntu-latest - if: github.event.release.assets[0] + if: | + github.event.release.assets[0] needs: get-release-branch env: TAG: ${{ github.event.release.tag_name }} @@ -95,11 +152,12 @@ jobs: path: ./changelog.txt upload: - name: Upload Gutenberg Plugin + name: Publish as trunk (and tag) runs-on: ubuntu-latest environment: wp.org plugin - needs: update-changelog - if: ${{ !github.event.release.prerelease && github.event.release.assets[0] }} + needs: [compute-should-update-trunk, update-changelog] + if: | + needs.compute-should-update-trunk.outputs.should_update_trunk == 'true' && github.event.release.assets[0] env: PLUGIN_REPO_URL: 'https://plugins.svn.wordpress.org/gutenberg' STABLE_VERSION_REGEX: '[0-9]\+\.[0-9]\+\.[0-9]\+\s*' @@ -109,11 +167,7 @@ jobs: steps: - name: Check out Gutenberg trunk from WP.org plugin repo - run: svn checkout "$PLUGIN_REPO_URL/trunk" - - - name: Get previous stable version - id: get_previous_stable_version - run: echo "stable_version=$(awk -F ':\ ' '$1 == "Stable tag" {print $2}' ./trunk/readme.txt)" >> $GITHUB_OUTPUT + run: svn checkout "$PLUGIN_REPO_URL/trunk" --username "$SVN_USERNAME" --password "$SVN_PASSWORD" - name: Delete everything working-directory: ./trunk @@ -130,8 +184,8 @@ jobs: - name: Replace the stable tag placeholder with the existing stable tag on the SVN repository env: STABLE_TAG_PLACEHOLDER: 'Stable tag: V\.V\.V' - STABLE_TAG: 'Stable tag: ${{ steps.get_previous_stable_version.outputs.stable_version }}' - run: sed -i "s/${STABLE_TAG_PLACEHOLDER}/${STABLE_TAG}/g" ./trunk/readme.txt + run: | + sed -i "s/$STABLE_TAG_PLACEHOLDER/Stable tag: $VERSION/g" ./trunk/readme.txt - name: Download Changelog Artifact uses: actions/download-artifact@9782bd6a9848b53b110e712e20e42d89988822b7 # v3.0.1 @@ -159,3 +213,44 @@ jobs: sed -i "s/Stable tag: ${STABLE_VERSION_REGEX}/Stable tag: ${VERSION}/g" ./readme.txt svn commit -m "Releasing version $VERSION" \ --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD" + + upload-tag: + name: Publish as tag + runs-on: ubuntu-latest + environment: wp.org plugin + needs: [compute-should-update-trunk, update-changelog] + if: | + needs.compute-should-update-trunk.outputs.should_update_trunk == 'false' && github.event.release.assets[0] + env: + PLUGIN_REPO_URL: 'https://plugins.svn.wordpress.org/gutenberg' + STABLE_VERSION_REGEX: '[0-9]\+\.[0-9]\+\.[0-9]\+\s*' + SVN_USERNAME: ${{ secrets.svn_username }} + SVN_PASSWORD: ${{ secrets.svn_password }} + VERSION: ${{ github.event.release.name }} + + steps: + - name: Download and unzip Gutenberg plugin asset into tags folder + env: + PLUGIN_URL: ${{ github.event.release.assets[0].browser_download_url }} + run: | + # do the magic here + curl -L -o gutenberg.zip $PLUGIN_URL + unzip gutenberg.zip -d "$VERSION" + rm gutenberg.zip + + - name: Replace the stable tag placeholder with the existing stable tag on the SVN repository + env: + STABLE_TAG_PLACEHOLDER: 'Stable tag: V\.V\.V' + run: | + sed -i "s/$STABLE_TAG_PLACEHOLDER/Stable tag: $VERSION/g" "$VERSION/readme.txt" + + - name: Download Changelog Artifact + uses: actions/download-artifact@9782bd6a9848b53b110e712e20e42d89988822b7 # v3.0.1 + with: + name: changelog trunk + path: ${{ github.event.release.name }} + + - name: Add the new version directory and commit changes to the SVN repository + run: | + svn import "$VERSION" "$PLUGIN_REPO_URL/tags/$VERSION" -m "Committing version $VERSION" \ + --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD" diff --git a/.gitignore b/.gitignore index 19e43aecea7b82..b44eabe00cccc7 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,7 @@ yarn.lock Thumbs.db # Report generated from jest-junit -test/native/junit.xml +junit.xml # Local overrides .wp-env.override.json diff --git a/bin/cherry-pick.mjs b/bin/cherry-pick.mjs index 36829db6ee5cf8..baf8d42962f8e8 100644 --- a/bin/cherry-pick.mjs +++ b/bin/cherry-pick.mjs @@ -114,12 +114,25 @@ async function fetchPRs() { const { items } = await GitHubFetch( `/search/issues?q=is:pr state:closed sort:updated label:"${ LABEL }" repo:WordPress/gutenberg` ); - const PRs = items.map( ( { id, number, title } ) => ( { + const PRs = items.map( ( { id, number, title, pull_request, closed_at } ) => ( { id, number, title, - } ) ); - console.log( 'Found the following PRs to cherry-pick: ' ); + closed_at, + pull_request, + } ) ).sort( ( a, b ) => { + /* + * `closed_at` and `pull_request.merged_at` are _usually_ the same, + * but let's prefer the latter if it's available. + */ + if ( a?.pull_request?.merged_at && b?.pull_request?.merged_at ) { + return new Date( a?.pull_request?.merged_at ) - new Date( b?.pull_request?.merged_at ); + } + return new Date( a.closed_at ) - new Date( b.closed_at ); + } ); + + + console.log( 'Found the following PRs to cherry-pick (sorted by closed date in ascending order): ' ); PRs.forEach( ( { number, title } ) => console.log( indent( `#${ number } – ${ title }` ) ) ); diff --git a/bin/test-create-block.sh b/bin/test-create-block.sh index e17bdbb2d66946..7959334a8e30ec 100755 --- a/bin/test-create-block.sh +++ b/bin/test-create-block.sh @@ -55,7 +55,7 @@ if [ "$expected" -ne "$actual" ]; then error "Expected $expected files in the project root, but found $actual." exit 1 fi -expected=6 +expected=7 actual=$( find src -maxdepth 1 -type f | wc -l ) if [ "$expected" -ne "$actual" ]; then error "Expected $expected files in the \`src\` directory, but found $actual." @@ -69,7 +69,7 @@ status "Building block..." ../node_modules/.bin/wp-scripts build status "Verifying build..." -expected=5 +expected=7 actual=$( find build -maxdepth 1 -type f | wc -l ) if [ "$expected" -ne "$actual" ]; then error "Expected $expected files in the \`build\` directory, but found $actual." diff --git a/changelog.txt b/changelog.txt index e011668bc7e00c..375a1d7199ece0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,719 @@ == Changelog == += 16.2.1 = + +## Changelog + +### Enhancements + +#### Footnotes +- Footnotes: show in inserter and placeholder. ([52445](https://github.com/WordPress/gutenberg/pull/52445)) + +### Bug Fixes + +#### Patterns +- Make Pattern title text clickable. ([52599](https://github.com/WordPress/gutenberg/pull/52599)) + +## Contributors + +The following contributors merged PRs in this release: + +@getdave @mcsf + + += 16.2.0 = + +## Changelog + +### Bug Fixes + +- Library - make pattern title clickable. ([51898](https://github.com/WordPress/gutenberg/pull/51898)) + +#### Block Library +- Allow editing existing footnote from RichText formats toolbar. ([52506](https://github.com/WordPress/gutenberg/pull/52506)) +- Revert "Post editor: Require confirmation before removing Footnotes (#52277)". ([52486](https://github.com/WordPress/gutenberg/pull/52486)) +- Trim footnote anchors from excerpts. ([52518](https://github.com/WordPress/gutenberg/pull/52518)) + +#### Interactivity API +- Fix md5 class messed up with new block key. ([52557](https://github.com/WordPress/gutenberg/pull/52557)) + +#### Block Editor +- Site Editor: Restore quick inserter 'Browse all' button. ([52529](https://github.com/WordPress/gutenberg/pull/52529)) + +#### Patterns +- Check that core hasn't already moved sync status meta before moving and unsetting. ([52494](https://github.com/WordPress/gutenberg/pull/52494)) + + +### Various + +- Rename block theme activation nonce variable. ([52398](https://github.com/WordPress/gutenberg/pull/52398)) + + + + +## Contributors + +The following contributors merged PRs in this release: + +@getdave @glendaviesnz @luisherranz @Mamaduka @mcsf @peterwilsoncc @priethor += 16.2.0-rc.3 = + +## Changelog + +### Bug Fixes + +#### Build Tooling +- Revert phpcs testVersion back to PHP 5.6. ([52384](https://github.com/WordPress/gutenberg/pull/52384)) + + += 16.2.0-rc.2 = + +## Changelog + +### Bug Fixes + +#### Block Library +- RichText/Footnotes: Make getRichTextValues work with InnerBlocks.Content. ([52241](https://github.com/WordPress/gutenberg/pull/52241)) + +#### Blocks +- Post and Comment Template blocks: Change render_block_context priority to 1 (https://github.com/WordPress/gutenberg/pull/52364) +- Footnotes: Fix incorrect anchor position in Firefox (https://github.com/WordPress/gutenberg/pull/52425) +- Footnotes: fix lingering format boundary attr (https://github.com/WordPress/gutenberg/pull/52439) +- Footnotes: save numbering through the entity provider (https://github.com/WordPress/gutenberg/pull/52423) + +#### Code Quality / Performance +- Iframe: avoid asset parsing & fix script localisation + +## Contributors + +The following contributors merged PRs in this release: + +@ellatrix @ockham @t-hamano + + += 16.2.0-rc.1 = + + + +## Changelog + +### Features + +#### Patterns +- feat: Rename Reusable blocks to Patterns. ([51704](https://github.com/WordPress/gutenberg/pull/51704)) + + +### Enhancements + +- Export store for the core/customize-widgets package. ([52189](https://github.com/WordPress/gutenberg/pull/52189)) + +#### Patterns +- Library: Add sync status to pattern details screen. ([51954](https://github.com/WordPress/gutenberg/pull/51954)) +- Rename Library to Patterns. ([52102](https://github.com/WordPress/gutenberg/pull/52102)) +- Update custom patterns label to 'My patterns'. ([51949](https://github.com/WordPress/gutenberg/pull/51949)) +- Update pattern creation modal in library. ([51946](https://github.com/WordPress/gutenberg/pull/51946)) +- Update template part icons in the library mosaic (grid items). ([51963](https://github.com/WordPress/gutenberg/pull/51963)) + +#### Site Editor +- Change "Home" template name to "Blog home". ([52048](https://github.com/WordPress/gutenberg/pull/52048)) +- Edit Site: Make loading spinner colors consistent. ([51857](https://github.com/WordPress/gutenberg/pull/51857)) +- Update the icon used to reference the blog. ([52075](https://github.com/WordPress/gutenberg/pull/52075)) + +#### Interactivity API +- Image block: Remove extra lookup for external image dimensions in lightbox. ([52178](https://github.com/WordPress/gutenberg/pull/52178)) +- Image block: Use built-in directive for mouseover event in lightbox. ([52067](https://github.com/WordPress/gutenberg/pull/52067)) + +#### Block Library +- Force full height for editor in Navigation focus mode. ([51798](https://github.com/WordPress/gutenberg/pull/51798)) +- Social links: Updating class and style attributes. ([51997](https://github.com/WordPress/gutenberg/pull/51997)) + +#### Themes +- Add border theme_support. ([51777](https://github.com/WordPress/gutenberg/pull/51777)) +- Add link color theme_support. ([51775](https://github.com/WordPress/gutenberg/pull/51775)) + +#### Global Styles +- Style Book: Show tabs and make blocks clickable when entering edit mode from the Styles menu. ([52222](https://github.com/WordPress/gutenberg/pull/52222)) + +#### Widgets Editor +- Add @example tags to the customize-widgets package. ([52141](https://github.com/WordPress/gutenberg/pull/52141)) + +#### NUX +- Page Content Focus: Add welcome guides. ([52014](https://github.com/WordPress/gutenberg/pull/52014)) + +#### Block Editor +- Use block label and icon for the inserter draggable chip.. ([51048](https://github.com/WordPress/gutenberg/pull/51048)) + +#### Design Tools +- Add Typography: Text orientation (writing mode). ([50822](https://github.com/WordPress/gutenberg/pull/50822)) + +#### Components +- RangeControl: Add support for large 40px number input size. ([49105](https://github.com/WordPress/gutenberg/pull/49105)) + + +### New APIs + +#### Block Editor +- Add new `registerInserterMediaCategory` API to make media categories extensible. ([51542](https://github.com/WordPress/gutenberg/pull/51542)) + + +### Bug Fixes + +- Adjust the position of sticky headings in preferences modal. ([52248](https://github.com/WordPress/gutenberg/pull/52248)) +- BlockRemovalWarningModal: Fix incorrect '_n' usage. ([52164](https://github.com/WordPress/gutenberg/pull/52164)) +- Editor initrial appender: Zero out margins in constrained layouts. ([52026](https://github.com/WordPress/gutenberg/pull/52026)) +- Export store from the edit-site package. ([51986](https://github.com/WordPress/gutenberg/pull/51986)) +- Fix disable DFM when opening styles command. ([52165](https://github.com/WordPress/gutenberg/pull/52165)) +- Fix unintentional toggling on of distraction free. ([52090](https://github.com/WordPress/gutenberg/pull/52090)) +- Footnotes: Increase selector specificity for anchor. ([52179](https://github.com/WordPress/gutenberg/pull/52179)) +- Respect custom aspect ratio. ([52286](https://github.com/WordPress/gutenberg/pull/52286)) +- Turn off DFM for style book and style editing. ([52117](https://github.com/WordPress/gutenberg/pull/52117)) +- Update fixed block toolbar. ([52123](https://github.com/WordPress/gutenberg/pull/52123)) +- Updating the BlockEditorProvider settings prop should reset the store's settings entirely. ([51904](https://github.com/WordPress/gutenberg/pull/51904)) +- [Command Palette]: Remove suggestion for deleting templates/parts. ([52168](https://github.com/WordPress/gutenberg/pull/52168)) +- [Command center]: Add preferences and keyboard shortcuts commands. ([51862](https://github.com/WordPress/gutenberg/pull/51862)) +- [Edit Post]: Add toggle fullscreen mode and list view commands. ([52184](https://github.com/WordPress/gutenberg/pull/52184)) + +#### Block Library +- Fix default block dimensions visibility. ([52256](https://github.com/WordPress/gutenberg/pull/52256)) +- Fix fetching Nav fallback ID flushing Navigation entity cache. ([52069](https://github.com/WordPress/gutenberg/pull/52069)) +- Fix flaky tests in `navigation.spec.js` and other tests related to the Post Editor Template mode. ([51790](https://github.com/WordPress/gutenberg/pull/51790)) +- Fix: Term Description block should only be available in the site editor. ([51053](https://github.com/WordPress/gutenberg/pull/51053)) +- Footnotes: Register meta field for pages. ([52024](https://github.com/WordPress/gutenberg/pull/52024)) +- Image block: Fix cursor style when lightbox is opened. ([52187](https://github.com/WordPress/gutenberg/pull/52187)) +- Navigation: Add the draft status to the navigation title. ([51967](https://github.com/WordPress/gutenberg/pull/51967)) +- Navigation: Fix end-to-end test failures caused by sidebar title change. ([52308](https://github.com/WordPress/gutenberg/pull/52308)) +- Navigation: Fix sidebar title. ([52167](https://github.com/WordPress/gutenberg/pull/52167)) +- Navigation: Remove one preloaded endpoint. ([52115](https://github.com/WordPress/gutenberg/pull/52115)) +- Page List: Fix parent block selection when converting to link. ([52193](https://github.com/WordPress/gutenberg/pull/52193)) +- Post editor: Require confirmation before removing Footnotes. ([52277](https://github.com/WordPress/gutenberg/pull/52277)) +- fix: Display heading level dropdown icons and labels. ([52004](https://github.com/WordPress/gutenberg/pull/52004)) + +#### Site Editor +- Add confirmation step when deleting a Template. ([52236](https://github.com/WordPress/gutenberg/pull/52236)) +- Command Palette: Fix incorrect path and snackbar message when template part is deleted. ([52034](https://github.com/WordPress/gutenberg/pull/52034)) +- Default to showing status slug in sidebar. ([52226](https://github.com/WordPress/gutenberg/pull/52226)) +- Fix missing MenuGroup segment in Site Editor header more menu. ([51860](https://github.com/WordPress/gutenberg/pull/51860)) +- Fix missing snackbars in Library. ([52021](https://github.com/WordPress/gutenberg/pull/52021)) +- Fix stepper styling in Home template sidebar. ([52025](https://github.com/WordPress/gutenberg/pull/52025)) +- Get the top toolbar preference from the correct scope. ([51840](https://github.com/WordPress/gutenberg/pull/51840)) +- Hide word count and reading time meta data for the Posts Page details panel. ([52186](https://github.com/WordPress/gutenberg/pull/52186)) +- Modal: Add small top padding to the content so that avoid cutting off the visible outline when hovering items. ([51829](https://github.com/WordPress/gutenberg/pull/51829)) +- Site Editor Frame: Ignore Spotlight in view mode. ([52262](https://github.com/WordPress/gutenberg/pull/52262)) +- Try restoring the site editor animation. ([51956](https://github.com/WordPress/gutenberg/pull/51956)) + +#### Patterns +- Fix custom patterns console error. ([51947](https://github.com/WordPress/gutenberg/pull/51947)) +- Fix history back after entering edit mode from Patterns. ([52112](https://github.com/WordPress/gutenberg/pull/52112)) +- Fix setting of sync status for fully synced patterns. ([51952](https://github.com/WordPress/gutenberg/pull/51952)) +- Fix sidebar tab label. ([51953](https://github.com/WordPress/gutenberg/pull/51953)) +- Fix: Pattern focus mode DocumentActions should use the pattern icon. ([52031](https://github.com/WordPress/gutenberg/pull/52031)) +- Include template parts for custom areas in Uncategorized category. ([52159](https://github.com/WordPress/gutenberg/pull/52159)) +- Remove ability for user to toggle sync status after pattern creation. ([51998](https://github.com/WordPress/gutenberg/pull/51998)) +- Rename sync_status and move to top level field on rest return instead of a meta field. ([52146](https://github.com/WordPress/gutenberg/pull/52146)) + +#### Interactivity API +- Block Image: Lightbox - Hide animation selector if behavior is Default or None. ([51748](https://github.com/WordPress/gutenberg/pull/51748)) +- Image block: Fix responsive sizing in lightbox. ([51823](https://github.com/WordPress/gutenberg/pull/51823)) +- Image block: Lightbox animation improvements. ([51721](https://github.com/WordPress/gutenberg/pull/51721)) +- Navigation block: Check that the modal is set before using `contains`. ([51962](https://github.com/WordPress/gutenberg/pull/51962)) + +#### Accessibility +- Fix incorrect aria-describedby attributes for theme patterns. ([52263](https://github.com/WordPress/gutenberg/pull/52263)) +- Guide: Place focus on the guide's container instead of its first tabbable. ([52300](https://github.com/WordPress/gutenberg/pull/52300)) +- Site Editor: Update headings hierarchy in the 'Manage all' screens. ([52271](https://github.com/WordPress/gutenberg/pull/52271)) + +#### Global Styles +- Check if experiment enabled for realsies this time. ([52315](https://github.com/WordPress/gutenberg/pull/52315)) +- Check randomizer experiment is enabled before rendering button. ([52306](https://github.com/WordPress/gutenberg/pull/52306)) + +#### Navigation Menu Sidebar +- Make the entire preview clickable in order to enter "edit" mode in focus mode. ([51973](https://github.com/WordPress/gutenberg/pull/51973)) +- Restore sidebar in focus mode on Pattern click through in Browse Mode `Library`. ([51897](https://github.com/WordPress/gutenberg/pull/51897)) + +#### Page Content Focus +- Hide parent selector when parent's block editing mode is 'disabled' or 'contentOnly'. ([52264](https://github.com/WordPress/gutenberg/pull/52264)) + +#### Post Editor +- Editor: Avoid remounting pre-publish sidebar contents during autosave. ([52208](https://github.com/WordPress/gutenberg/pull/52208)) + +#### Block Editor +- Enable draft entity creation in Nav block offcanvas. ([52166](https://github.com/WordPress/gutenberg/pull/52166)) + +#### History +- Update the behavior of the cached undo/redo stack. ([51644](https://github.com/WordPress/gutenberg/pull/51644)) + +#### Components +- DropdownMenu: Fix icon style when dashicon is used. ([43574](https://github.com/WordPress/gutenberg/pull/43574)) + + +### Performance + +- Migrate performance tests to Playwright. ([51084](https://github.com/WordPress/gutenberg/pull/51084)) +- Social links: Reverts updating class and style attributes. ([52019](https://github.com/WordPress/gutenberg/pull/52019)) +- tests: Configure as a production environment. ([52016](https://github.com/WordPress/gutenberg/pull/52016)) + +#### Block Library +- Try: Aggressive TinyMCE deprecation. ([50387](https://github.com/WordPress/gutenberg/pull/50387)) + + +### Experiments + +#### Interactivity API +- Create @wordpress/interactivity with the Interactivity API. ([50906](https://github.com/WordPress/gutenberg/pull/50906)) + + +### Documentation + +- Add @examples to the @wordpress/rich-text package selectors and hide the actions from documentation. ([52089](https://github.com/WordPress/gutenberg/pull/52089)) +- Add examples for core/keyboard-shortcut package. ([42831](https://github.com/WordPress/gutenberg/pull/42831)) +- Block Editor: Add README for FontFamilyControl component. ([52118](https://github.com/WordPress/gutenberg/pull/52118)) +- Block Editor: Add README for `PanelColorSettings` component. ([52327](https://github.com/WordPress/gutenberg/pull/52327)) +- Block Editor: Add README for `RecursionProvider`. ([52334](https://github.com/WordPress/gutenberg/pull/52334)) +- Docs: Update release documentation to use the right cherry-picking command. ([51935](https://github.com/WordPress/gutenberg/pull/51935)) + + +### Code Quality + +- Lodash: Refactor away from `_.kebabCase()` in `getCleanTemplatePartSlug`. ([51906](https://github.com/WordPress/gutenberg/pull/51906)) +- Lodash: Refactor away from `_.kebabCase()` in add page modal. ([51911](https://github.com/WordPress/gutenberg/pull/51911)) +- Lodash: Refactor away from `_.kebabCase()` in generic template modal. ([51910](https://github.com/WordPress/gutenberg/pull/51910)) +- Lodash: Remove completely from `@wordpress/style-engine` package. ([51726](https://github.com/WordPress/gutenberg/pull/51726)) + +#### Block Library +- Heading Block: Remove unused `HeadingLevelIcon` component. ([52008](https://github.com/WordPress/gutenberg/pull/52008)) +- Image block and behaviors: Fix some warnings. ([52109](https://github.com/WordPress/gutenberg/pull/52109)) +- Lodash: Refactor embed block away from `_.kebabCase()`. ([51916](https://github.com/WordPress/gutenberg/pull/51916)) +- Lodash: Remove dependency from block library package. ([51976](https://github.com/WordPress/gutenberg/pull/51976)) +- Make Navigation fallback selector private. ([51413](https://github.com/WordPress/gutenberg/pull/51413)) +- Page List: Fix ESLint warnings. ([52267](https://github.com/WordPress/gutenberg/pull/52267)) +- Refactor, document, and fix image block deprecations. ([52081](https://github.com/WordPress/gutenberg/pull/52081)) + +#### Page Content Focus +- Add basic test for the page content focus flow. ([52231](https://github.com/WordPress/gutenberg/pull/52231)) + +#### List View +- Return primitive value for 'hideInserter' in Appender component. ([52161](https://github.com/WordPress/gutenberg/pull/52161)) + +#### Interactivity API +- Fix the `exsisting` -> `existing` typo. ([52110](https://github.com/WordPress/gutenberg/pull/52110)) + +#### Navigation Menu Sidebar +- Remove redundant call to Navigation selector in Browse Mode. ([51988](https://github.com/WordPress/gutenberg/pull/51988)) + +#### Site Editor +- Block removal prompt: Let consumers pass their own rules. ([51841](https://github.com/WordPress/gutenberg/pull/51841)) + +#### Block Editor +- Revise LinkControl suggestions UI to use MenuItem. ([50978](https://github.com/WordPress/gutenberg/pull/50978)) + + +### Tools + +#### Testing +- Drops PHP 5.6 CI jobs. ([52345](https://github.com/WordPress/gutenberg/pull/52345)) +- Fix flakiness of saving entities in the site editor. ([51728](https://github.com/WordPress/gutenberg/pull/51728)) +- Fix flaky Site Editor pages end-to-end test. ([52283](https://github.com/WordPress/gutenberg/pull/52283)) +- Have `createNewPost` wait for editor canvas contents. ([51824](https://github.com/WordPress/gutenberg/pull/51824)) + +#### Build Tooling +- Fix phpunit failures. ([51950](https://github.com/WordPress/gutenberg/pull/51950)) +- Use moment-timezone-data-webpack-plugin to optimize timezones shipped in wp/date. ([51519](https://github.com/WordPress/gutenberg/pull/51519)) + + +### Various + +- Add caching to schema of REST API. ([52045](https://github.com/WordPress/gutenberg/pull/52045)) +- Add code owners for the Interactivity API runtime. ([52174](https://github.com/WordPress/gutenberg/pull/52174)) +- Backport from core: Rename `gutenberg_get_remote_theme_patterns` to `gutenberg_get_theme_directory_pattern_slugs`. ([51784](https://github.com/WordPress/gutenberg/pull/51784)) +- Block editor store: Also attach private APIs to old store descriptor. ([52088](https://github.com/WordPress/gutenberg/pull/52088)) +- Blocks: Remove gutenberg refs in PHP files. ([51978](https://github.com/WordPress/gutenberg/pull/51978)) +- Command palette: Rename. ([52153](https://github.com/WordPress/gutenberg/pull/52153)) +- Drop-indicator: Remove white border. ([52122](https://github.com/WordPress/gutenberg/pull/52122)) +- First version of the Interactivity API README. ([52104](https://github.com/WordPress/gutenberg/pull/52104)) +- Global Styles Revisions API: Backport changes from Core. ([52095](https://github.com/WordPress/gutenberg/pull/52095)) +- Global Styles Sidebar: Re-add Colors: Heading to selected blocks. ([49131](https://github.com/WordPress/gutenberg/pull/49131)) +- Image block: Update lightbox animation tests. ([52290](https://github.com/WordPress/gutenberg/pull/52290)) +- Patterns: Update section heading levels. ([52273](https://github.com/WordPress/gutenberg/pull/52273)) +- Perf logging: Change date to ISO 8601. ([51833](https://github.com/WordPress/gutenberg/pull/51833)) +- Refactor use-tab-nav shift+tab to use existing utils. ([51817](https://github.com/WordPress/gutenberg/pull/51817)) +- Remove serverSideBlockDefinitions from a test. ([52215](https://github.com/WordPress/gutenberg/pull/52215)) +- Restore "Buttons > can resize width" test. ([51865](https://github.com/WordPress/gutenberg/pull/51865)) +- Update delete page button label. ([51812](https://github.com/WordPress/gutenberg/pull/51812)) +- Update versions in WP for 6.3. ([51984](https://github.com/WordPress/gutenberg/pull/51984)) +- Wrap "Move to trash" and "Switch to draft" buttons when labels are too long to fit on a single row. ([52249](https://github.com/WordPress/gutenberg/pull/52249)) +- [Github-Actions-Workflows][Plugin-Release] Allow shipping a point-release for an older stable release. ([49082](https://github.com/WordPress/gutenberg/pull/49082)) + +#### Block Library +- Block Editor: Unify texts for Create pattern modal. ([52151](https://github.com/WordPress/gutenberg/pull/52151)) +- Block Supports: Change prefix in gutenberg_apply_colors_support to wp_ in dynamic blocks. ([51989](https://github.com/WordPress/gutenberg/pull/51989)) +- Navigation in Site View: Readd the edit button. ([52111](https://github.com/WordPress/gutenberg/pull/52111)) +- Navigation submenu: Remove unused doc block. ([52152](https://github.com/WordPress/gutenberg/pull/52152)) +- Page List: Change modal text. ([52116](https://github.com/WordPress/gutenberg/pull/52116)) +- i18n: Add context to the word "Filters". ([52198](https://github.com/WordPress/gutenberg/pull/52198)) + +#### Site Editor +- Library: Update icons in the creation menu. ([52108](https://github.com/WordPress/gutenberg/pull/52108)) +- Polish welcome guide copy for page / template editing. ([52282](https://github.com/WordPress/gutenberg/pull/52282)) +- Try: Update template titles. ([51428](https://github.com/WordPress/gutenberg/pull/51428)) +- Update stepper styling in Home template details panel. ([51972](https://github.com/WordPress/gutenberg/pull/51972)) +- Update text color of active menu items. ([51965](https://github.com/WordPress/gutenberg/pull/51965)) + +#### Patterns +- Add a hint about the rename of reusable blocks to menu and inserter. ([51771](https://github.com/WordPress/gutenberg/pull/51771)) +- Copy: "Detach pattern" instead of "Covert to regular block". ([51993](https://github.com/WordPress/gutenberg/pull/51993)) +- Library: Reinstate manage all template parts page. ([51961](https://github.com/WordPress/gutenberg/pull/51961)) +- [Library] Add lock icon for theme patterns. ([51990](https://github.com/WordPress/gutenberg/pull/51990)) + +#### Accessibility +- Navigation block: Do not toggle aria-expanded on hover when the overlay menu is opened. ([52170](https://github.com/WordPress/gutenberg/pull/52170)) +- Navigation block: Don't close submenu when it has focus. ([52177](https://github.com/WordPress/gutenberg/pull/52177)) + +#### Widgets Editor +- Export the store for the core/edit-widgets pacakage. ([52190](https://github.com/WordPress/gutenberg/pull/52190)) + +#### Post Editor +- Move block editor settings filter into 6.3 compat folder. ([52100](https://github.com/WordPress/gutenberg/pull/52100)) + +#### Layout +- Move grid function kses patch into 6.3 compat folder. ([52098](https://github.com/WordPress/gutenberg/pull/52098)) + +#### Data Layer +- hasResolvingSelectors: Exclude from result of resolveSelect. ([52038](https://github.com/WordPress/gutenberg/pull/52038)) + +#### Icons +- Remove fill="none" from pinSmall icon. ([51979](https://github.com/WordPress/gutenberg/pull/51979)) + +#### Navigation Menu Sidebar +- Sidebar Navigation: Refactor delete modal with `ConfirmDialog` component. ([51867](https://github.com/WordPress/gutenberg/pull/51867)) + +#### Templates API +- Template revisions API: Move back to experimental. ([51774](https://github.com/WordPress/gutenberg/pull/51774)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @ajlende @annezazu @artemiomorales @c4rl0sbr4v0 @carolinan @DAreRodz @dcalhoun @draganescu @ellatrix @fullofcaffeine @getdave @glendaviesnz @hellofromtonya @jameskoster @jasmussen @jeryj @jsnajdr @juanfra @juanmaguitar @kevin940726 @luisherranz @Mamaduka @mcsf @michalczaplinski @miminari @noisysocks @ntsekouras @oandregal @ockham @priethor @ramonjd @richtabor @ryanwelcher @SaxonF @scruffian @spacedmonkey @stokesman @t-hamano @talldan @tellthemachines @tyxla @WunderBart @youknowriad + + + + += 16.1.2 = + +### Bug fixes + +* Footnotes: increase selector specificity for anchor (https://github.com/WordPress/gutenberg/pull/52179) + + += 16.2.0-rc.1 = + + + +## Changelog + +### Features + +#### Patterns +- feat: Rename Reusable blocks to Patterns. ([51704](https://github.com/WordPress/gutenberg/pull/51704)) + + +### Enhancements + +- Export store for the core/customize-widgets package. ([52189](https://github.com/WordPress/gutenberg/pull/52189)) + +#### Patterns +- Library: Add sync status to pattern details screen. ([51954](https://github.com/WordPress/gutenberg/pull/51954)) +- Rename Library to Patterns. ([52102](https://github.com/WordPress/gutenberg/pull/52102)) +- Update custom patterns label to 'My patterns'. ([51949](https://github.com/WordPress/gutenberg/pull/51949)) +- Update pattern creation modal in library. ([51946](https://github.com/WordPress/gutenberg/pull/51946)) +- Update template part icons in the library mosaic (grid items). ([51963](https://github.com/WordPress/gutenberg/pull/51963)) + +#### Site Editor +- Change "Home" template name to "Blog home". ([52048](https://github.com/WordPress/gutenberg/pull/52048)) +- Edit Site: Make loading spinner colors consistent. ([51857](https://github.com/WordPress/gutenberg/pull/51857)) +- Update the icon used to reference the blog. ([52075](https://github.com/WordPress/gutenberg/pull/52075)) + +#### Interactivity API +- Image block: Remove extra lookup for external image dimensions in lightbox. ([52178](https://github.com/WordPress/gutenberg/pull/52178)) +- Image block: Use built-in directive for mouseover event in lightbox. ([52067](https://github.com/WordPress/gutenberg/pull/52067)) + +#### Block Library +- Force full height for editor in Navigation focus mode. ([51798](https://github.com/WordPress/gutenberg/pull/51798)) +- Social links: Updating class and style attributes. ([51997](https://github.com/WordPress/gutenberg/pull/51997)) + +#### Themes +- Add border theme_support. ([51777](https://github.com/WordPress/gutenberg/pull/51777)) +- Add link color theme_support. ([51775](https://github.com/WordPress/gutenberg/pull/51775)) + +#### Global Styles +- Style Book: Show tabs and make blocks clickable when entering edit mode from the Styles menu. ([52222](https://github.com/WordPress/gutenberg/pull/52222)) + +#### Widgets Editor +- Add @example tags to the customize-widgets package. ([52141](https://github.com/WordPress/gutenberg/pull/52141)) + +#### NUX +- Page Content Focus: Add welcome guides. ([52014](https://github.com/WordPress/gutenberg/pull/52014)) + +#### Block Editor +- Use block label and icon for the inserter draggable chip.. ([51048](https://github.com/WordPress/gutenberg/pull/51048)) + +#### Design Tools +- Add Typography: Text orientation (writing mode). ([50822](https://github.com/WordPress/gutenberg/pull/50822)) + +#### Components +- RangeControl: Add support for large 40px number input size. ([49105](https://github.com/WordPress/gutenberg/pull/49105)) + + +### New APIs + +#### Block Editor +- Add new `registerInserterMediaCategory` API to make media categories extensible. ([51542](https://github.com/WordPress/gutenberg/pull/51542)) + + +### Bug Fixes + +- Adjust the position of sticky headings in preferences modal. ([52248](https://github.com/WordPress/gutenberg/pull/52248)) +- BlockRemovalWarningModal: Fix incorrect '_n' usage. ([52164](https://github.com/WordPress/gutenberg/pull/52164)) +- Editor initrial appender: Zero out margins in constrained layouts. ([52026](https://github.com/WordPress/gutenberg/pull/52026)) +- Export store from the edit-site package. ([51986](https://github.com/WordPress/gutenberg/pull/51986)) +- Fix disable DFM when opening styles command. ([52165](https://github.com/WordPress/gutenberg/pull/52165)) +- Fix unintentional toggling on of distraction free. ([52090](https://github.com/WordPress/gutenberg/pull/52090)) +- Footnotes: Increase selector specificity for anchor. ([52179](https://github.com/WordPress/gutenberg/pull/52179)) +- Respect custom aspect ratio. ([52286](https://github.com/WordPress/gutenberg/pull/52286)) +- Turn off DFM for style book and style editing. ([52117](https://github.com/WordPress/gutenberg/pull/52117)) +- Update fixed block toolbar. ([52123](https://github.com/WordPress/gutenberg/pull/52123)) +- Updating the BlockEditorProvider settings prop should reset the store's settings entirely. ([51904](https://github.com/WordPress/gutenberg/pull/51904)) +- [Command Palette]: Remove suggestion for deleting templates/parts. ([52168](https://github.com/WordPress/gutenberg/pull/52168)) +- [Command center]: Add preferences and keyboard shortcuts commands. ([51862](https://github.com/WordPress/gutenberg/pull/51862)) +- [Edit Post]: Add toggle fullscreen mode and list view commands. ([52184](https://github.com/WordPress/gutenberg/pull/52184)) + +#### Block Library +- Fix default block dimensions visibility. ([52256](https://github.com/WordPress/gutenberg/pull/52256)) +- Fix fetching Nav fallback ID flushing Navigation entity cache. ([52069](https://github.com/WordPress/gutenberg/pull/52069)) +- Fix flaky tests in `navigation.spec.js` and other tests related to the Post Editor Template mode. ([51790](https://github.com/WordPress/gutenberg/pull/51790)) +- Fix: Term Description block should only be available in the site editor. ([51053](https://github.com/WordPress/gutenberg/pull/51053)) +- Footnotes: Register meta field for pages. ([52024](https://github.com/WordPress/gutenberg/pull/52024)) +- Image block: Fix cursor style when lightbox is opened. ([52187](https://github.com/WordPress/gutenberg/pull/52187)) +- Navigation: Add the draft status to the navigation title. ([51967](https://github.com/WordPress/gutenberg/pull/51967)) +- Navigation: Fix end-to-end test failures caused by sidebar title change. ([52308](https://github.com/WordPress/gutenberg/pull/52308)) +- Navigation: Fix sidebar title. ([52167](https://github.com/WordPress/gutenberg/pull/52167)) +- Navigation: Remove one preloaded endpoint. ([52115](https://github.com/WordPress/gutenberg/pull/52115)) +- Page List: Fix parent block selection when converting to link. ([52193](https://github.com/WordPress/gutenberg/pull/52193)) +- Post editor: Require confirmation before removing Footnotes. ([52277](https://github.com/WordPress/gutenberg/pull/52277)) +- fix: Display heading level dropdown icons and labels. ([52004](https://github.com/WordPress/gutenberg/pull/52004)) + +#### Site Editor +- Add confirmation step when deleting a Template. ([52236](https://github.com/WordPress/gutenberg/pull/52236)) +- Command Palette: Fix incorrect path and snackbar message when template part is deleted. ([52034](https://github.com/WordPress/gutenberg/pull/52034)) +- Default to showing status slug in sidebar. ([52226](https://github.com/WordPress/gutenberg/pull/52226)) +- Fix missing MenuGroup segment in Site Editor header more menu. ([51860](https://github.com/WordPress/gutenberg/pull/51860)) +- Fix missing snackbars in Library. ([52021](https://github.com/WordPress/gutenberg/pull/52021)) +- Fix stepper styling in Home template sidebar. ([52025](https://github.com/WordPress/gutenberg/pull/52025)) +- Get the top toolbar preference from the correct scope. ([51840](https://github.com/WordPress/gutenberg/pull/51840)) +- Hide word count and reading time meta data for the Posts Page details panel. ([52186](https://github.com/WordPress/gutenberg/pull/52186)) +- Modal: Add small top padding to the content so that avoid cutting off the visible outline when hovering items. ([51829](https://github.com/WordPress/gutenberg/pull/51829)) +- Site Editor Frame: Ignore Spotlight in view mode. ([52262](https://github.com/WordPress/gutenberg/pull/52262)) +- Try restoring the site editor animation. ([51956](https://github.com/WordPress/gutenberg/pull/51956)) + +#### Patterns +- Fix custom patterns console error. ([51947](https://github.com/WordPress/gutenberg/pull/51947)) +- Fix history back after entering edit mode from Patterns. ([52112](https://github.com/WordPress/gutenberg/pull/52112)) +- Fix setting of sync status for fully synced patterns. ([51952](https://github.com/WordPress/gutenberg/pull/51952)) +- Fix sidebar tab label. ([51953](https://github.com/WordPress/gutenberg/pull/51953)) +- Fix: Pattern focus mode DocumentActions should use the pattern icon. ([52031](https://github.com/WordPress/gutenberg/pull/52031)) +- Include template parts for custom areas in Uncategorized category. ([52159](https://github.com/WordPress/gutenberg/pull/52159)) +- Remove ability for user to toggle sync status after pattern creation. ([51998](https://github.com/WordPress/gutenberg/pull/51998)) +- Rename sync_status and move to top level field on rest return instead of a meta field. ([52146](https://github.com/WordPress/gutenberg/pull/52146)) + +#### Interactivity API +- Block Image: Lightbox - Hide animation selector if behavior is Default or None. ([51748](https://github.com/WordPress/gutenberg/pull/51748)) +- Image block: Fix responsive sizing in lightbox. ([51823](https://github.com/WordPress/gutenberg/pull/51823)) +- Image block: Lightbox animation improvements. ([51721](https://github.com/WordPress/gutenberg/pull/51721)) +- Navigation block: Check that the modal is set before using `contains`. ([51962](https://github.com/WordPress/gutenberg/pull/51962)) + +#### Accessibility +- Fix incorrect aria-describedby attributes for theme patterns. ([52263](https://github.com/WordPress/gutenberg/pull/52263)) +- Guide: Place focus on the guide's container instead of its first tabbable. ([52300](https://github.com/WordPress/gutenberg/pull/52300)) +- Site Editor: Update headings hierarchy in the 'Manage all' screens. ([52271](https://github.com/WordPress/gutenberg/pull/52271)) + +#### Global Styles +- Check if experiment enabled for realsies this time. ([52315](https://github.com/WordPress/gutenberg/pull/52315)) +- Check randomizer experiment is enabled before rendering button. ([52306](https://github.com/WordPress/gutenberg/pull/52306)) + +#### Navigation Menu Sidebar +- Make the entire preview clickable in order to enter "edit" mode in focus mode. ([51973](https://github.com/WordPress/gutenberg/pull/51973)) +- Restore sidebar in focus mode on Pattern click through in Browse Mode `Library`. ([51897](https://github.com/WordPress/gutenberg/pull/51897)) + +#### Page Content Focus +- Hide parent selector when parent's block editing mode is 'disabled' or 'contentOnly'. ([52264](https://github.com/WordPress/gutenberg/pull/52264)) + +#### Post Editor +- Editor: Avoid remounting pre-publish sidebar contents during autosave. ([52208](https://github.com/WordPress/gutenberg/pull/52208)) + +#### Block Editor +- Enable draft entity creation in Nav block offcanvas. ([52166](https://github.com/WordPress/gutenberg/pull/52166)) + +#### History +- Update the behavior of the cached undo/redo stack. ([51644](https://github.com/WordPress/gutenberg/pull/51644)) + +#### Components +- DropdownMenu: Fix icon style when dashicon is used. ([43574](https://github.com/WordPress/gutenberg/pull/43574)) + + +### Performance + +- Migrate performance tests to Playwright. ([51084](https://github.com/WordPress/gutenberg/pull/51084)) +- Social links: Reverts updating class and style attributes. ([52019](https://github.com/WordPress/gutenberg/pull/52019)) +- tests: Configure as a production environment. ([52016](https://github.com/WordPress/gutenberg/pull/52016)) + +#### Block Library +- Try: Aggressive TinyMCE deprecation. ([50387](https://github.com/WordPress/gutenberg/pull/50387)) + + +### Experiments + +#### Interactivity API +- Create @wordpress/interactivity with the Interactivity API. ([50906](https://github.com/WordPress/gutenberg/pull/50906)) + + +### Documentation + +- Add @examples to the @wordpress/rich-text package selectors and hide the actions from documentation. ([52089](https://github.com/WordPress/gutenberg/pull/52089)) +- Add examples for core/keyboard-shortcut package. ([42831](https://github.com/WordPress/gutenberg/pull/42831)) +- Block Editor: Add README for FontFamilyControl component. ([52118](https://github.com/WordPress/gutenberg/pull/52118)) +- Block Editor: Add README for `PanelColorSettings` component. ([52327](https://github.com/WordPress/gutenberg/pull/52327)) +- Block Editor: Add README for `RecursionProvider`. ([52334](https://github.com/WordPress/gutenberg/pull/52334)) +- Docs: Update release documentation to use the right cherry-picking command. ([51935](https://github.com/WordPress/gutenberg/pull/51935)) + + +### Code Quality + +- Lodash: Refactor away from `_.kebabCase()` in `getCleanTemplatePartSlug`. ([51906](https://github.com/WordPress/gutenberg/pull/51906)) +- Lodash: Refactor away from `_.kebabCase()` in add page modal. ([51911](https://github.com/WordPress/gutenberg/pull/51911)) +- Lodash: Refactor away from `_.kebabCase()` in generic template modal. ([51910](https://github.com/WordPress/gutenberg/pull/51910)) +- Lodash: Remove completely from `@wordpress/style-engine` package. ([51726](https://github.com/WordPress/gutenberg/pull/51726)) + +#### Block Library +- Heading Block: Remove unused `HeadingLevelIcon` component. ([52008](https://github.com/WordPress/gutenberg/pull/52008)) +- Image block and behaviors: Fix some warnings. ([52109](https://github.com/WordPress/gutenberg/pull/52109)) +- Lodash: Refactor embed block away from `_.kebabCase()`. ([51916](https://github.com/WordPress/gutenberg/pull/51916)) +- Lodash: Remove dependency from block library package. ([51976](https://github.com/WordPress/gutenberg/pull/51976)) +- Make Navigation fallback selector private. ([51413](https://github.com/WordPress/gutenberg/pull/51413)) +- Page List: Fix ESLint warnings. ([52267](https://github.com/WordPress/gutenberg/pull/52267)) +- Refactor, document, and fix image block deprecations. ([52081](https://github.com/WordPress/gutenberg/pull/52081)) + +#### Page Content Focus +- Add basic test for the page content focus flow. ([52231](https://github.com/WordPress/gutenberg/pull/52231)) + +#### List View +- Return primitive value for 'hideInserter' in Appender component. ([52161](https://github.com/WordPress/gutenberg/pull/52161)) + +#### Interactivity API +- Fix the `exsisting` -> `existing` typo. ([52110](https://github.com/WordPress/gutenberg/pull/52110)) + +#### Navigation Menu Sidebar +- Remove redundant call to Navigation selector in Browse Mode. ([51988](https://github.com/WordPress/gutenberg/pull/51988)) + +#### Site Editor +- Block removal prompt: Let consumers pass their own rules. ([51841](https://github.com/WordPress/gutenberg/pull/51841)) + +#### Block Editor +- Revise LinkControl suggestions UI to use MenuItem. ([50978](https://github.com/WordPress/gutenberg/pull/50978)) + + +### Tools + +#### Testing +- Drops PHP 5.6 CI jobs. ([52345](https://github.com/WordPress/gutenberg/pull/52345)) +- Fix flakiness of saving entities in the site editor. ([51728](https://github.com/WordPress/gutenberg/pull/51728)) +- Fix flaky Site Editor pages end-to-end test. ([52283](https://github.com/WordPress/gutenberg/pull/52283)) +- Have `createNewPost` wait for editor canvas contents. ([51824](https://github.com/WordPress/gutenberg/pull/51824)) + +#### Build Tooling +- Fix phpunit failures. ([51950](https://github.com/WordPress/gutenberg/pull/51950)) +- Use moment-timezone-data-webpack-plugin to optimize timezones shipped in wp/date. ([51519](https://github.com/WordPress/gutenberg/pull/51519)) + + +### Various + +- Add caching to schema of REST API. ([52045](https://github.com/WordPress/gutenberg/pull/52045)) +- Add code owners for the Interactivity API runtime. ([52174](https://github.com/WordPress/gutenberg/pull/52174)) +- Backport from core: Rename `gutenberg_get_remote_theme_patterns` to `gutenberg_get_theme_directory_pattern_slugs`. ([51784](https://github.com/WordPress/gutenberg/pull/51784)) +- Block editor store: Also attach private APIs to old store descriptor. ([52088](https://github.com/WordPress/gutenberg/pull/52088)) +- Blocks: Remove gutenberg refs in PHP files. ([51978](https://github.com/WordPress/gutenberg/pull/51978)) +- Command palette: Rename. ([52153](https://github.com/WordPress/gutenberg/pull/52153)) +- Drop-indicator: Remove white border. ([52122](https://github.com/WordPress/gutenberg/pull/52122)) +- First version of the Interactivity API README. ([52104](https://github.com/WordPress/gutenberg/pull/52104)) +- Global Styles Revisions API: Backport changes from Core. ([52095](https://github.com/WordPress/gutenberg/pull/52095)) +- Global Styles Sidebar: Re-add Colors: Heading to selected blocks. ([49131](https://github.com/WordPress/gutenberg/pull/49131)) +- Image block: Update lightbox animation tests. ([52290](https://github.com/WordPress/gutenberg/pull/52290)) +- Patterns: Update section heading levels. ([52273](https://github.com/WordPress/gutenberg/pull/52273)) +- Perf logging: Change date to ISO 8601. ([51833](https://github.com/WordPress/gutenberg/pull/51833)) +- Refactor use-tab-nav shift+tab to use existing utils. ([51817](https://github.com/WordPress/gutenberg/pull/51817)) +- Remove serverSideBlockDefinitions from a test. ([52215](https://github.com/WordPress/gutenberg/pull/52215)) +- Restore "Buttons > can resize width" test. ([51865](https://github.com/WordPress/gutenberg/pull/51865)) +- Update delete page button label. ([51812](https://github.com/WordPress/gutenberg/pull/51812)) +- Update versions in WP for 6.3. ([51984](https://github.com/WordPress/gutenberg/pull/51984)) +- Wrap "Move to trash" and "Switch to draft" buttons when labels are too long to fit on a single row. ([52249](https://github.com/WordPress/gutenberg/pull/52249)) +- [Github-Actions-Workflows][Plugin-Release] Allow shipping a point-release for an older stable release. ([49082](https://github.com/WordPress/gutenberg/pull/49082)) + +#### Block Library +- Block Editor: Unify texts for Create pattern modal. ([52151](https://github.com/WordPress/gutenberg/pull/52151)) +- Block Supports: Change prefix in gutenberg_apply_colors_support to wp_ in dynamic blocks. ([51989](https://github.com/WordPress/gutenberg/pull/51989)) +- Navigation in Site View: Readd the edit button. ([52111](https://github.com/WordPress/gutenberg/pull/52111)) +- Navigation submenu: Remove unused doc block. ([52152](https://github.com/WordPress/gutenberg/pull/52152)) +- Page List: Change modal text. ([52116](https://github.com/WordPress/gutenberg/pull/52116)) +- i18n: Add context to the word "Filters". ([52198](https://github.com/WordPress/gutenberg/pull/52198)) + +#### Site Editor +- Library: Update icons in the creation menu. ([52108](https://github.com/WordPress/gutenberg/pull/52108)) +- Polish welcome guide copy for page / template editing. ([52282](https://github.com/WordPress/gutenberg/pull/52282)) +- Try: Update template titles. ([51428](https://github.com/WordPress/gutenberg/pull/51428)) +- Update stepper styling in Home template details panel. ([51972](https://github.com/WordPress/gutenberg/pull/51972)) +- Update text color of active menu items. ([51965](https://github.com/WordPress/gutenberg/pull/51965)) + +#### Patterns +- Add a hint about the rename of reusable blocks to menu and inserter. ([51771](https://github.com/WordPress/gutenberg/pull/51771)) +- Copy: "Detach pattern" instead of "Covert to regular block". ([51993](https://github.com/WordPress/gutenberg/pull/51993)) +- Library: Reinstate manage all template parts page. ([51961](https://github.com/WordPress/gutenberg/pull/51961)) +- [Library] Add lock icon for theme patterns. ([51990](https://github.com/WordPress/gutenberg/pull/51990)) + +#### Accessibility +- Navigation block: Do not toggle aria-expanded on hover when the overlay menu is opened. ([52170](https://github.com/WordPress/gutenberg/pull/52170)) +- Navigation block: Don't close submenu when it has focus. ([52177](https://github.com/WordPress/gutenberg/pull/52177)) + +#### Widgets Editor +- Export the store for the core/edit-widgets pacakage. ([52190](https://github.com/WordPress/gutenberg/pull/52190)) + +#### Post Editor +- Move block editor settings filter into 6.3 compat folder. ([52100](https://github.com/WordPress/gutenberg/pull/52100)) + +#### Layout +- Move grid function kses patch into 6.3 compat folder. ([52098](https://github.com/WordPress/gutenberg/pull/52098)) + +#### Data Layer +- hasResolvingSelectors: Exclude from result of resolveSelect. ([52038](https://github.com/WordPress/gutenberg/pull/52038)) + +#### Icons +- Remove fill="none" from pinSmall icon. ([51979](https://github.com/WordPress/gutenberg/pull/51979)) + +#### Navigation Menu Sidebar +- Sidebar Navigation: Refactor delete modal with `ConfirmDialog` component. ([51867](https://github.com/WordPress/gutenberg/pull/51867)) + +#### Templates API +- Template revisions API: Move back to experimental. ([51774](https://github.com/WordPress/gutenberg/pull/51774)) + + + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @ajlende @annezazu @artemiomorales @c4rl0sbr4v0 @carolinan @DAreRodz @dcalhoun @draganescu @ellatrix @fullofcaffeine @getdave @glendaviesnz @hellofromtonya @jameskoster @jasmussen @jeryj @jsnajdr @juanfra @juanmaguitar @kevin940726 @luisherranz @Mamaduka @mcsf @michalczaplinski @miminari @noisysocks @ntsekouras @oandregal @ockham @priethor @ramonjd @richtabor @ryanwelcher @SaxonF @scruffian @spacedmonkey @stokesman @t-hamano @talldan @tellthemachines @tyxla @WunderBart @youknowriad + + = 16.1.1 = ## Changelog diff --git a/docs/assets/text-transform-component.png b/docs/assets/text-transform-component.png new file mode 100644 index 00000000000000..2155fbdb8ebef6 Binary files /dev/null and b/docs/assets/text-transform-component.png differ diff --git a/docs/contributors/code/react-native/getting-started-react-native.md b/docs/contributors/code/react-native/getting-started-react-native.md index 96338af7a6f2f7..9b8ae44f0d8b18 100644 --- a/docs/contributors/code/react-native/getting-started-react-native.md +++ b/docs/contributors/code/react-native/getting-started-react-native.md @@ -89,7 +89,7 @@ One of the extensions we are using is the [React Native Tools](https://marketpla Use the following command to run the test suite: ```sh -npm run native test +npm run test:native ``` It will run the [jest](https://github.com/facebook/jest) test runner on your tests. The tests are running on the desktop against Node.js. @@ -97,7 +97,7 @@ It will run the [jest](https://github.com/facebook/jest) test runner on your tes To run the tests with debugger support, start it with the following CLI command: ```sh -npm run native test:debug +npm run test:native:debug ``` Then, open `chrome://inspect` in Chrome to attach the debugger (look into the "Remote Target" section). While testing/developing, feel free to sprinkle `debugger` statements anywhere in the code that you'd like the debugger to break. diff --git a/docs/contributors/code/react-native/osx-setup-guide.md b/docs/contributors/code/react-native/osx-setup-guide.md index 5d6d4603e49598..dab5f33448b74d 100644 --- a/docs/contributors/code/react-native/osx-setup-guide.md +++ b/docs/contributors/code/react-native/osx-setup-guide.md @@ -212,7 +212,7 @@ After a bit of a wait, we’ll see something like this: ## Unit Tests ```sh -npm run native test +npm run test:native ``` ## Integration Tests diff --git a/docs/contributors/code/release.md b/docs/contributors/code/release.md index 180de30d195571..edbd67655a6990 100644 --- a/docs/contributors/code/release.md +++ b/docs/contributors/code/release.md @@ -181,7 +181,13 @@ _If_ however, the previous release was an **RC** (e.g. `X.Y.0-rc.1`) you will ne To do this, when running the Workflow, select the appropriate `release/` branch from the `Use workflow from` dropdown (e.g. `release/12.5`) and specify `stable` in the text input field. -Please note you **cannot create minor releases for previous stable releases once a more recent stable release has been published** as this would require significant changes to how we upload plugin versions to the WP.org plugin SVN repo). Always check the latest release version before you proceed (see [this Issue](https://github.com/WordPress/gutenberg/issues/33277#issuecomment-876289457) for more information). +##### Creating a minor release for previous stable releases + +It is possible to create a minor release for any release branch even after a more recent stable release has been published. This can be done for _any_ previous release branches, allowing more flexibility in delivering updates to users. In the past, users had to wait for the next stable release, potentially taking days. Now, fixes can be swiftly shipped to any previous release branches as required. + +The process is identical to the one documented above when an RC is already out: choose a previous release branch, type `stable`, and click "Run workflow". The release will be published on the GitHub releases page for Gutenberg and to the WordPress core repository SVN as a `tag` under http://plugins.svn.wordpress.org/gutenberg/tags/. The SVN `trunk` directory will not be touched. + +**IMPORTANT:** When publishing the draft created by the ["Build Plugin Zip" workflow](https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml), make sure to leave the "Set as last release" checkbox unchecked. If it is left checked by accident, the ["Upload Gutenberg plugin to WordPress.org plugin" workflow](https://github.com/WordPress/gutenberg/actions/workflows/upload-release-to-plugin-repo.yml) will still correctly upload it **as a tag (and will _not_ replace the `trunk` version)** to the WordPress plugin repository SVN - the workflow will perform some version arithmetic to determine how the plugin should be shipped - but you'll still need to fix the state on GitHub by setting the right release as `latest` on the [releases](https://github.com/WordPress/gutenberg/releases/) page! #### Troubleshooting diff --git a/docs/contributors/code/scripts.md b/docs/contributors/code/scripts.md index 5cd7efd2fffdad..1483a409a4d08f 100644 --- a/docs/contributors/code/scripts.md +++ b/docs/contributors/code/scripts.md @@ -31,6 +31,7 @@ The editor includes a number of packages to enable various pieces of functionali | [Is Shallow Equal](/packages/is-shallow-equal/README.md) | wp-is-shallow-equal | A function for performing a shallow comparison between two objects or arrays | | [Keycodes](/packages/keycodes/README.md) | wp-keycodes | Keycodes utilities for WordPress, used to check the key pressed in events like `onKeyDown` | | [List Reusable blocks](/packages/list-reusable-blocks/README.md) | wp-list-reusable-blocks | Package used to add import/export links to the listing page of the reusable blocks | +| [NUX](/packages/nux/README.md) | wp-nux | Components, and wp.data methods useful for onboarding a new user to the WordPress admin interface | | [Plugins](/packages/plugins/README.md) | wp-plugins | Plugins module for WordPress | | [Redux Routine](/packages/redux-routine/README.md) | wp-redux-routine | Redux middleware for generator coroutines | | [Rich Text](/packages/rich-text/README.md) | wp-rich-text | Helper functions to convert HTML or a DOM tree into a rich text value and back | diff --git a/docs/contributors/code/testing-overview.md b/docs/contributors/code/testing-overview.md index 42704be233b9b9..946aec8cd70e3e 100644 --- a/docs/contributors/code/testing-overview.md +++ b/docs/contributors/code/testing-overview.md @@ -497,7 +497,7 @@ Part of the unit-tests suite is a set of Jest tests run exercise native-mobile c To locally run the tests in debug mode, follow these steps: 0. Make sure you have ran `npm ci` to install all the packages -1. Run `npm run native test:debug` inside the Gutenberg root folder, on the CLI. Node is now waiting for the debugger to connect. +1. Run `npm run test:native:debug` inside the Gutenberg root folder, on the CLI. Node is now waiting for the debugger to connect. 2. Open `chrome://inspect` in Chrome 3. Under the "Remote Target" section, look for a `../../node_modules/.bin/jest` target and click on the "inspect" link. That will open a new window with the Chrome DevTools debugger attached to the process and stopped at the beginning of the `jest.js` file. Alternatively, if the targets are not visible, click on the `Open dedicated DevTools for Node` link in the same page. 4. You can place breakpoints or `debugger;` statements throughout the code, including the tests code, to stop and inspect diff --git a/docs/explanations/architecture/entities.md b/docs/explanations/architecture/entities.md index 13e6eaca08b5a0..2a3af6288d27fa 100644 --- a/docs/explanations/architecture/entities.md +++ b/docs/explanations/architecture/entities.md @@ -56,8 +56,14 @@ For example, let's say a user edits the title of a post, followed by a modificat The store also keep tracks of a "pointer" to the current "undo/redo" step. By default, the pointer always points to the last item in the stack. This pointer is updated when the user performs an undo or redo operation. -### Transient changes +### Cached changes -The undo/redo core behavior also supports what we call "transient modifications". These are modifications that are not stored in the undo/redo stack right away. For instance, when a user starts typing in a text field, the value of the field is modified in the store, but this modification is not stored in the undo/redo stack until after the user moves to the next word or after a few milliseconds. This is done to avoid creating a new undo/redo step for each character typed by the user. +The undo/redo core behavior also supports what we call "cached modifications". These are modifications that are not stored in the undo/redo stack right away. For instance, when a user starts typing in a text field, the value of the field is modified in the store, but this modification is not stored in the undo/redo stack until after the user moves to the next word or after a few milliseconds. This is done to avoid creating a new undo/redo step for each character typed by the user. -So by default, `core-data` store considers all modifications to properties that are marked as "transient" (like the `blocks` property in the post entity) as transient modifications. It keeps these modifications outside the undo/redo stack in what is called a "cache" of modifications and these modifications are only stored in the undo/redo stack when we explicitely call `__unstableCreateUndoLevel` or when the next non-transient modification is performed. +Cached changes are kept outside the undo/redo stack in what is called a "cache" of modifications and these modifications are only stored in the undo/redo stack when we explicitely call `__unstableCreateUndoLevel` or when the next modification is not a cached one. + +By default all calls to `editEntityRecord` are considered "non-cached" unless the `isCached` option is passed as true. Example: + +```js +wp.data.dispatch( 'core' ).editEntityRecord( 'postType', 'post', 1, { title: 'Hello World' }, { isCached: true } ); +``` diff --git a/docs/explanations/architecture/styles.md b/docs/explanations/architecture/styles.md index faf2649efd13ab..a8a5af72fec76b 100644 --- a/docs/explanations/architecture/styles.md +++ b/docs/explanations/architecture/styles.md @@ -59,10 +59,8 @@ The user may change the state of this block by applying different styles: a text After some user modifications to the block, the initial markup may become something like this: ```html -

+

``` This is what we refer to as "user-provided block styles", also know as "local styles" or "serialized styles". Essentially, each tool (font size, color, etc) ends up adding some classes and/or inline styles to the block markup. The CSS styling for these classes is part of the block, global, or theme stylesheets. @@ -511,9 +509,7 @@ The global styles UI in the site editor has a screen for per-block styles. The l In addition to styles at the individual block level and in global styles, there is the concept of layout styles that are output for both blocks-based and classic themes. -The layout block support is an experimental approach for outputting common layout styles that are shared between blocks that are used for creating layouts. Layout styles are useful for providing common styling for any block that is a container for other blocks. Examples of blocks that depend on these layout styles include Group, Row, Columns, Buttons, and Social Icons. - -While the feature is part of WordPress core, it is still flagged as experimental in the sense that the features and output are still undergoing active development. It is therefore not yet a stable feature from the perspective of 3rd party blocks, as the API is likely to change. The feature is enabled in core blocks via the `layout` setting under `supports` in a block's `block.json` file. +The layout block support outputs common layout styles that are shared between blocks used for creating layouts. Layout styles are useful for providing common styling for any block that is a container for other blocks. Examples of blocks that depend on these layout styles include Group, Row, Columns, Buttons, and Social Icons. The feature is enabled in core blocks via the `layout` setting under `supports` in a block's `block.json` file. There are two primary places where Layout styles are output: @@ -523,22 +519,23 @@ Base layout styles are those styles that are common to all blocks that opt in to Base layout styles are output from within [the main PHP class](https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/class-wp-theme-json.php) that handles global styles, and form part of the global styles stylesheet. In order to provide support for core blocks in classic themes, these styles are always output, irrespective of whether the theme provides its own `theme.json` file. -Common layout definitions are stored in [the core `theme.json` file](https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/theme.json), but are not intended to be extended or overridden by themes. +Common layout definitions are stored in [the core layout block support file](https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/block-supports/layout.php). #### Individual layout styles -When a block that opts in to the experimental layout support is rendered, two things are processed and added to the output via [`layout.php`](https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/block-supports/layout.php): +When a block that opts in to layout support is rendered, two things are processed and added to the output via [`layout.php`](https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/block-supports/layout.php): - Semantic class names are added to the block markup to indicate which layout settings are in use. For example, `is-layout-flow` is for blocks (such as Group) that use the default/flow layout, and `is-content-justification-right` is added when a user sets a block to use right justification. - Individual styles are generated for non-default layout values that are set on the individual block being rendered. These styles are attached to the block via a container class name using the form `wp-container-$id` where the `$id` is a [unique number](https://developer.wordpress.org/reference/functions/wp_unique_id/). #### Available layout types -There are currently three layout types in use: +There are currently four layout types in use: - Default/Flow: Items are stacked vertically. The parent container block is set to `display: flow` and the spacing between children is handled via vertical margins. - Constrained: Items are stacked vertically, using the same spacing logic as the Flow layout. Features constrained widths for child content, outputting widths for standard content size and wide size. Defaults to using global `contentSize` and `wideSize` values set in `settings.layout` in the `theme.json`. - Flex: Items are displayed using a Flexbox layout. Defaults to a horizontal orientation. Spacing between children is handled via the `gap` CSS property. +- Grid: Items are displayed using a Grid layout. Defaults to an `auto-fill` approach to column generation but can also be set to a fixed number of columns. Spacing between children is handled via the `gap` CSS property. For controlling spacing between blocks, and enabling block spacing controls see: [What is blockGap and how can I use it?](https://developer.wordpress.org/block-editor/how-to-guides/themes/theme-json/#what-is-blockgap-and-how-can-i-use-it). @@ -546,7 +543,7 @@ For controlling spacing between blocks, and enabling block spacing controls see: The layout block support is designed to enable control over layout features from within the block and site editors. Where possible, try to use the features of the blocks to determine particular layout requirements rather than relying upon additional stylesheets. -For themes that wish to target container blocks in order to add or adjust particular styles, the block's class name is often the best class name to use. Class names such as `wp-block-group` or `wp-block-columns` are usually reliable class names for targeting a particular block. +For themes that wish to target container blocks in order to add or adjust particular styles, the block's class name is often the best class name to use. Class names such as `wp-block-group` or `wp-block-columns` are usually reliable class names for targeting a particular block. In addition to block and layout classnames, there is also a classname composed of block and layout together: for example, for a Group block with a constrained layout it will be `wp-block-group-is-layout-constrained`. For targeting a block that uses a particular layout type, avoid targeting `wp-container-` as container classes may not always be present in the rendered markup. @@ -559,6 +556,7 @@ The current semantic class names that can be output by the Layout block support - `is-layout-flow`: Blocks that use the Default/Flow layout type. - `is-layout-constrained`: Blocks that use the Constrained layout type. - `is-layout-flex`: Blocks that use the Flex layout type. +- `is-layout-grid`: Blocks that used the Grid layout type. - `wp-container-$id`: Where `$id` is a semi-random number. A container class that only exists when the block contains non-default Layout values. This class should not be used directly for any CSS targeting as it may or may not be present. - `is-horizontal`: When a block explicitly sets `orientation` to `horizontal`. - `is-vertical`: When a block explicitly sets `orientation` to `vertical`. diff --git a/docs/manifest.json b/docs/manifest.json index aaadd15a570790..5f75e49924f355 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1505,6 +1505,12 @@ "markdown_source": "../packages/core-data/README.md", "parent": "packages" }, + { + "title": "@wordpress/create-block-interactive-template", + "slug": "packages-create-block-interactive-template", + "markdown_source": "../packages/create-block-interactive-template/README.md", + "parent": "packages" + }, { "title": "@wordpress/create-block-tutorial-template", "slug": "packages-create-block-tutorial-template", @@ -1751,6 +1757,12 @@ "markdown_source": "../packages/npm-package-json-lint-config/README.md", "parent": "packages" }, + { + "title": "@wordpress/nux", + "slug": "packages-nux", + "markdown_source": "../packages/nux/README.md", + "parent": "packages" + }, { "title": "@wordpress/plugins", "slug": "packages-plugins", @@ -1997,6 +2009,12 @@ "markdown_source": "../docs/reference-guides/data/data-core-notices.md", "parent": "data" }, + { + "title": "The NUX (New User Experience) Data", + "slug": "data-core-nux", + "markdown_source": "../docs/reference-guides/data/data-core-nux.md", + "parent": "data" + }, { "title": "Preferences", "slug": "data-core-preferences", diff --git a/docs/reference-guides/README.md b/docs/reference-guides/README.md index f13c838697f2de..33fdd9aa602414 100644 --- a/docs/reference-guides/README.md +++ b/docs/reference-guides/README.md @@ -63,6 +63,7 @@ - [**core/editor**: The Post Editor’s Data](/docs/reference-guides/data/data-core-editor.md) - [**core/keyboard-shortcuts**: The Keyboard Shortcuts Data](/docs/reference-guides/data/data-core-keyboard-shortcuts.md) - [**core/notices**: Notices Data](/docs/reference-guides/data/data-core-notices.md) + - [**core/nux**: The NUX (New User Experience) Data](/docs/reference-guides/data/data-core-nux.md) - [**core/preferences**: Preferences](/docs/reference-guides/data/data-core-preferences.md) - [**core/reusable-blocks**: Reusable blocks](/docs/reference-guides/data/data-core-reusable-blocks.md) - [**core/rich-text**: Rich Text](/docs/reference-guides/data/data-core-rich-text.md) diff --git a/docs/reference-guides/block-api/block-supports.md b/docs/reference-guides/block-api/block-supports.md index 7e68e18eb14bf8..0995f4d86cfd76 100644 --- a/docs/reference-guides/block-api/block-supports.md +++ b/docs/reference-guides/block-api/block-supports.md @@ -543,6 +543,79 @@ supports: { } ``` +## layout + +- Type: `boolean` or `Object` +- Default value: null +- Subproperties: + - `default`: type `Object`, default value null + - `allowSwitching`: type `boolean`, default value `false` + - `allowEditing`: type `boolean`, default value `true` + - `allowInheriting`: type `boolean`, default value `true` + - `allowSizingOnChildren`: type `boolean`, default value `false` + - `allowVerticalAlignment`: type `boolean`, default value `true` + - `allowJustification`: type `boolean`, default value `true` + - `allowOrientation`: type `boolean`, default value `true` + +This value only applies to blocks that are containers for inner blocks. If set to `true` the layout type will be `flow`. For other layout types it's necessary to set the `type` explicitly inside the `default` object. + +### layout.default + +- Type: `Object` +- Default value: null + +Allows setting the `type` property to define what layout type is default for the block, and also default values for any properties inherent to that layout type, e.g., for a `flex` layout, a default value can be set for `flexWrap`. + +### layout.allowSwitching + +- Type: `boolean` +- Default value: `false` + +Exposes a switcher control that allows toggling between all existing layout types. + +### layout.allowEditing + +- Type: `boolean` +- Default value: `true` + +Determines display of layout controls in the block sidebar. If set to false, layout controls will be hidden. + +### layout.allowInheriting + +- Type: `boolean` +- Default value: `true` + +For the `flow` layout type only, determines display of the "Inner blocks use content width" toggle. + +### layout.allowSizingOnChildren + +- Type: `boolean` +- Default value: `false` + +For the `flex` layout type only, determines display of sizing controls (Fit/Fill/Fixed) on all child blocks of the flex block. + +### layout.allowVerticalAlignment + +- Type: `boolean` +- Default value: `true` + +For the `flex` layout type only, determines display of the vertical alignment control in the block toolbar. + +### layout.allowJustification + +- Type: `boolean` +- Default value: `true` + +For the `flex` layout type, determines display of the justification control in the block toolbar and block sidebar. For the `constrained` layout type, determines display of justification control in the block sidebar. + +### layout.allowOrientation + +- Type: `boolean` +- Default value: `true` + +For the `flex` layout type only, determines display of the orientation control in the block toolbar. + + ## multiple - Type: `boolean` diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index a4e307c94df87e..d0a6ca7d356b2f 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -38,7 +38,7 @@ Add a user’s avatar. ([Source](https://github.com/WordPress/gutenberg/tree/tru ## Pattern -Create and save content to reuse across your site. Update the block, and the changes apply everywhere it’s used. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/block)) +Create and save content to reuse across your site. Update the pattern, and the changes apply everywhere it’s used. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/block)) - **Name:** core/block - **Category:** reusable @@ -266,7 +266,7 @@ Add a link to a downloadable file. ([Source](https://github.com/WordPress/gutenb - **Name:** core/file - **Category:** media -- **Supports:** align, anchor, color (background, gradients, link, ~~text~~), interactivity +- **Supports:** align, anchor, color (background, gradients, link, ~~text~~) - **Attributes:** displayPreview, downloadButtonText, fileId, fileName, href, id, previewHeight, showDownloadButton, textLinkHref, textLinkTarget ## Footnotes @@ -275,7 +275,7 @@ Add a link to a downloadable file. ([Source](https://github.com/WordPress/gutenb - **Name:** core/footnotes - **Category:** text -- **Supports:** ~~html~~, ~~inserter~~, ~~multiple~~, ~~reusable~~ +- **Supports:** ~~html~~, ~~multiple~~, ~~reusable~~ - **Attributes:** ## Classic @@ -421,7 +421,7 @@ A collection of blocks that allow visitors to get around your site. ([Source](ht - **Name:** core/navigation - **Category:** theme -- **Supports:** align (full, wide), inserter, interactivity, layout (allowSizingOnChildren, default, ~~allowInheriting~~, ~~allowSwitching~~, ~~allowVerticalAlignment~~), spacing (blockGap, units), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** align (full, wide), inserter, layout (allowSizingOnChildren, default, ~~allowInheriting~~, ~~allowSwitching~~, ~~allowVerticalAlignment~~), spacing (blockGap, units), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** __unstableLocation, backgroundColor, customBackgroundColor, customOverlayBackgroundColor, customOverlayTextColor, customTextColor, hasIcon, icon, maxNestingLevel, openSubmenusOnClick, overlayBackgroundColor, overlayMenu, overlayTextColor, ref, rgbBackgroundColor, rgbTextColor, showSubmenuIcon, templateLock, textColor ## Custom Link diff --git a/docs/reference-guides/data/README.md b/docs/reference-guides/data/README.md index 5f4d8d92d4bd49..1134c1d5ddd307 100644 --- a/docs/reference-guides/data/README.md +++ b/docs/reference-guides/data/README.md @@ -12,6 +12,7 @@ - [**core/editor**: The Post Editor’s Data](/docs/reference-guides/data/data-core-editor.md) - [**core/keyboard-shortcuts**: The Keyboard Shortcuts Data](/docs/reference-guides/data/data-core-keyboard-shortcuts.md) - [**core/notices**: Notices Data](/docs/reference-guides/data/data-core-notices.md) +- [**core/nux**: The NUX (New User Experience) Data](/docs/reference-guides/data/data-core-nux.md) - [**core/preferences**: Preferences](/docs/reference-guides/data/data-core-preferences.md) - [**core/reusable-blocks**: Reusable blocks](/docs/reference-guides/data/data-core-reusable-blocks.md) - [**core/rich-text**: Rich Text](/docs/reference-guides/data/data-core-rich-text.md) diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md index 67dbc48ccf29d8..fd29b1c6e1388c 100644 --- a/docs/reference-guides/data/data-core-block-editor.md +++ b/docs/reference-guides/data/data-core-block-editor.md @@ -485,6 +485,29 @@ _Returns_ - `Array`: ids of top-level and descendant blocks. +### getDirectInsertBlock + +Returns the block to be directly inserted by the block appender. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _rootClientId_ `?string`: Optional root client ID of block list. + +_Returns_ + +- `?WPDirectInsertBlock`: The block type to be directly inserted. + +_Type Definition_ + +- _WPDirectInsertBlock_ `Object` + +_Properties_ + +- _name_ `string`: The type of block. +- _attributes_ `?Object`: Attributes to pass to the newly created block. +- _attributesToCopy_ `?Array`: Attributes to be copied from adjecent blocks when inserted. + ### getDraggedBlockClientIds Returns the client ids of any blocks being directly dragged. diff --git a/docs/reference-guides/data/data-core-nux.md b/docs/reference-guides/data/data-core-nux.md new file mode 100644 index 00000000000000..eb6a1c3b5c9a5b --- /dev/null +++ b/docs/reference-guides/data/data-core-nux.md @@ -0,0 +1,93 @@ +# The NUX (New User Experience) Data + +Namespace: `core/nux`. + +## Selectors + + + +### areTipsEnabled + +Returns whether or not tips are globally enabled. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: Whether tips are globally enabled. + +### getAssociatedGuide + +Returns an object describing the guide, if any, that the given tip is a part of. + +_Parameters_ + +- _state_ `Object`: Global application state. +- _tipId_ `string`: The tip to query. + +_Returns_ + +- `?NUXGuideInfo`: Information about the associated guide. + +### isTipVisible + +Determines whether or not the given tip is showing. Tips are hidden if they are disabled, have been dismissed, or are not the current tip in any guide that they have been added to. + +_Parameters_ + +- _state_ `Object`: Global application state. +- _tipId_ `string`: The tip to query. + +_Returns_ + +- `boolean`: Whether or not the given tip is showing. + + + +## Actions + + + +### disableTips + +Returns an action object that, when dispatched, prevents all tips from showing again. + +_Returns_ + +- `Object`: Action object. + +### dismissTip + +Returns an action object that, when dispatched, dismisses the given tip. A dismissed tip will not show again. + +_Parameters_ + +- _id_ `string`: The tip to dismiss. + +_Returns_ + +- `Object`: Action object. + +### enableTips + +Returns an action object that, when dispatched, makes all tips show again. + +_Returns_ + +- `Object`: Action object. + +### triggerGuide + +Returns an action object that, when dispatched, presents a guide that takes the user through a series of tips step by step. + +_Parameters_ + +- _tipIds_ `string[]`: Which tips to show in the guide. + +_Returns_ + +- `Object`: Action object. + + diff --git a/docs/reference-guides/filters/block-filters.md b/docs/reference-guides/filters/block-filters.md index e41215d67065a1..66e89b0a1d614a 100644 --- a/docs/reference-guides/filters/block-filters.md +++ b/docs/reference-guides/filters/block-filters.md @@ -63,11 +63,13 @@ function addListBlockClassName( settings, name ) { return settings; } - return lodash.assign( {}, settings, { - supports: lodash.assign( {}, settings.supports, { + return { + ...settings, + supports: { + ...settings.supports, className: true, - } ), - } ); + }, + }; } wp.hooks.addFilter( @@ -126,7 +128,10 @@ Adding a background by default to all blocks. ```js function addBackgroundColorStyle( props ) { - return lodash.assign( props, { style: { backgroundColor: 'red' } } ); + return { + ...props, + style: { backgroundColor: 'red' }, + }; } wp.hooks.addFilter( @@ -276,9 +281,10 @@ var withClientIdClassName = wp.compose.createHigherOrderComponent( function ( BlockListBlock ) { return function ( props ) { - var newProps = lodash.assign( {}, props, { + var newProps = { + ...props, className: 'block-' + props.clientId, - } ); + }; return el( BlockListBlock, newProps ); }; diff --git a/docs/toc.json b/docs/toc.json index 085bbb536ece2b..1660afdcc29497 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -284,6 +284,7 @@ "docs/reference-guides/data/data-core-keyboard-shortcuts.md": [] }, { "docs/reference-guides/data/data-core-notices.md": [] }, + { "docs/reference-guides/data/data-core-nux.md": [] }, { "docs/reference-guides/data/data-core-preferences.md": [] }, diff --git a/gutenberg.php b/gutenberg.php index 8037b9acba9c86..8a5b9e110a51c6 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality. * Requires at least: 6.1 * Requires PHP: 5.6 - * Version: 16.1.1 + * Version: 16.2.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/lib/blocks.php b/lib/blocks.php index 8185567db1b804..e98f711b5c85a5 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -372,3 +372,31 @@ function gutenberg_register_legacy_social_link_blocks() { } add_action( 'init', 'gutenberg_register_legacy_social_link_blocks' ); + +/** + * Migrate the legacy `sync_status` meta key (added 16.1) to the new `wp_pattern_sync_status` meta key (16.1.1). + * + * This filter is INTENTIONALLY left out of core as the meta key was fist introduced to core in 6.3 as `wp_pattern_sync_status`. + * see https://github.com/WordPress/gutenberg/pull/52232 + * + * @param mixed $value The value to return, either a single metadata value or an array of values depending on the value of $single. + * @param int $object_id ID of the object metadata is for. + * @param string $meta_key Metadata key. + * @param bool $single Whether to return only the first value of the specified $meta_key. + */ +function gutenberg_legacy_wp_block_post_meta( $value, $object_id, $meta_key, $single ) { + if ( 'wp_pattern_sync_status' !== $meta_key ) { + return $value; + } + + $sync_status = get_post_meta( $object_id, 'sync_status', $single ); + + if ( $single && 'unsynced' === $sync_status ) { + return $sync_status; + } elseif ( isset( $sync_status[0] ) && 'unsynced' === $sync_status[0] ) { + return $sync_status; + } + + return $value; +} +add_filter( 'default_post_metadata', 'gutenberg_legacy_wp_block_post_meta', 10, 4 ); diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 6e9d05cd7f238b..1e825e3c6bbe4f 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -275,7 +275,9 @@ public static function get_theme_data( $deprecated = array(), $options = array() } // BEGIN OF EXPERIMENTAL CODE. Not to backport to core. - static::$theme = WP_Fonts_Resolver::add_missing_fonts_to_theme_json( static::$theme ); + if ( ! class_exists( 'WP_Font_Face' ) && class_exists( 'WP_Fonts_Resolver' ) ) { + static::$theme = WP_Fonts_Resolver::add_missing_fonts_to_theme_json( static::$theme ); + } // END OF EXPERIMENTAL CODE. } diff --git a/lib/client-assets.php b/lib/client-assets.php index 99aa7f147ecbfc..891eb05eabdbeb 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -368,6 +368,15 @@ function gutenberg_register_packages_styles( $styles ) { ); $styles->add_data( 'wp-edit-blocks', 'rtl', 'replace' ); + gutenberg_override_style( + $styles, + 'wp-nux', + gutenberg_url( 'build/nux/style.css' ), + array( 'wp-components' ), + $version + ); + $styles->add_data( 'wp-nux', 'rtl', 'replace' ); + gutenberg_override_style( $styles, 'wp-block-library-theme', diff --git a/lib/compat/wordpress-6.3/blocks.php b/lib/compat/wordpress-6.3/blocks.php index b338d0a2467096..962c9ee3e07ef3 100644 --- a/lib/compat/wordpress-6.3/blocks.php +++ b/lib/compat/wordpress-6.3/blocks.php @@ -45,7 +45,7 @@ function gutenberg_rename_reusable_block_cpt_to_pattern( $args, $post_type ) { $args['labels']['singular_name'] = _x( 'Pattern', 'post type singular name' ); $args['labels']['add_new_item'] = __( 'Add new Pattern' ); $args['labels']['new_item'] = __( 'New Pattern' ); - $args['labels']['edit_item'] = __( 'Edit Pattern' ); + $args['labels']['edit_item'] = __( 'Edit Block Pattern' ); $args['labels']['view_item'] = __( 'View Pattern' ); $args['labels']['view_items'] = __( 'View Patterns' ); $args['labels']['all_items'] = __( 'All Patterns' ); @@ -60,6 +60,7 @@ function gutenberg_rename_reusable_block_cpt_to_pattern( $args, $post_type ) { $args['labels']['item_reverted_to_draft'] = __( 'Pattern reverted to draft.' ); $args['labels']['item_scheduled'] = __( 'Pattern scheduled.' ); $args['labels']['item_updated'] = __( 'Pattern updated.' ); + $args['rest_controller_class'] = 'Gutenberg_REST_Blocks_Controller'; } return $args; @@ -89,7 +90,7 @@ function gutenberg_add_custom_fields_to_wp_block( $args, $post_type ) { add_filter( 'register_post_type_args', 'gutenberg_add_custom_fields_to_wp_block', 10, 2 ); /** - * Adds sync_status meta fields to the wp_block post type so an unsynced option can be added. + * Adds wp_pattern_sync_status meta fields to the wp_block post type so an unsynced option can be added. * * Note: This should be removed when the minimum required WP version is >= 6.3. * @@ -101,39 +102,21 @@ function gutenberg_wp_block_register_post_meta() { $post_type = 'wp_block'; register_post_meta( $post_type, - 'sync_status', + 'wp_pattern_sync_status', array( 'auth_callback' => function() { return current_user_can( 'edit_posts' ); }, - 'sanitize_callback' => 'gutenberg_wp_block_sanitize_post_meta', + 'sanitize_callback' => 'sanitize_text_field', 'single' => true, 'type' => 'string', 'show_in_rest' => array( 'schema' => array( - 'type' => 'string', - 'properties' => array( - 'sync_status' => array( - 'type' => 'string', - ), - ), + 'type' => 'string', + 'enum' => array( 'partial', 'unsynced' ), ), ), ) ); } -/** - * Sanitizes the array of wp_block post meta sync_status string. - * - * Note: This should be removed when the minimum required WP version is >= 6.3. - * - * @see https://github.com/WordPress/gutenberg/pull/51144 - * - * @param array $meta_value String to sanitize. - * - * @return array Sanitized string. - */ -function gutenberg_wp_block_sanitize_post_meta( $meta_value ) { - return sanitize_text_field( $meta_value ); -} add_action( 'init', 'gutenberg_wp_block_register_post_meta' ); diff --git a/lib/compat/wordpress-6.3/class-gutenberg-navigation-fallback.php b/lib/compat/wordpress-6.3/class-gutenberg-navigation-fallback.php index 91417971e22c73..fcf6e13b0954d7 100644 --- a/lib/compat/wordpress-6.3/class-gutenberg-navigation-fallback.php +++ b/lib/compat/wordpress-6.3/class-gutenberg-navigation-fallback.php @@ -23,9 +23,18 @@ class Gutenberg_Navigation_Fallback { */ public static function get_fallback() { + /** + * Filters whether or not a fallback should be created. + * + * @since 6.3.0 + * + * @param bool Whether or not to create a fallback. + */ + $should_create_fallback = apply_filters( 'gutenberg_navigation_should_create_fallback', true ); + $fallback = static::get_most_recently_published_navigation(); - if ( $fallback ) { + if ( $fallback || ! $should_create_fallback ) { return $fallback; } diff --git a/lib/compat/wordpress-6.3/class-gutenberg-rest-blocks-controller.php b/lib/compat/wordpress-6.3/class-gutenberg-rest-blocks-controller.php new file mode 100644 index 00000000000000..5279a2c3a829ec --- /dev/null +++ b/lib/compat/wordpress-6.3/class-gutenberg-rest-blocks-controller.php @@ -0,0 +1,51 @@ +\s*\d+\s*_', + '', + $content + ); +} + +add_filter( 'the_content', 'gutenberg_trim_footnotes' ); diff --git a/lib/compat/wordpress-6.3/script-loader.php b/lib/compat/wordpress-6.3/script-loader.php index c515eb10fdc6bb..8f7bda2a648114 100644 --- a/lib/compat/wordpress-6.3/script-loader.php +++ b/lib/compat/wordpress-6.3/script-loader.php @@ -81,7 +81,6 @@ function _gutenberg_get_iframed_editor_assets() { ob_start(); wp_print_styles(); - wp_print_fonts( true ); $styles = ob_get_clean(); ob_start(); diff --git a/lib/compat/wordpress-6.3/theme-previews.php b/lib/compat/wordpress-6.3/theme-previews.php index eab05c5824b1ff..26153d74878b58 100644 --- a/lib/compat/wordpress-6.3/theme-previews.php +++ b/lib/compat/wordpress-6.3/theme-previews.php @@ -88,7 +88,7 @@ function addLivePreviewButton() { livePreviewButton.setAttribute('class', 'button button-primary'); livePreviewButton.setAttribute( 'href', - `/wp-admin/site-editor.php?wp_theme_preview=${themePath}&return=themes.php` + `?wp_theme_preview=${themePath}&return=themes.php` ); livePreviewButton.innerHTML = ''; themeInfo.querySelector('.theme-actions').appendChild(livePreviewButton); @@ -107,7 +107,7 @@ function block_theme_activate_nonce() { $nonce_handle = 'switch-theme_' . gutenberg_get_theme_preview_path(); ?> post_content; } + // Check if block editor is disabled by "Classic Editor" or another plugin. + if ( + function_exists( 'use_block_editor_for_post_type' ) && + ! use_block_editor_for_post_type( $current_post->post_type ) + ) { + return true; + } + if ( empty( $content ) ) { return false; } diff --git a/lib/experimental/fonts-api/fonts-api.php b/lib/experimental/fonts-api/fonts-api.php index 841efeda47a953..8d07dc118f56e1 100644 --- a/lib/experimental/fonts-api/fonts-api.php +++ b/lib/experimental/fonts-api/fonts-api.php @@ -26,14 +26,7 @@ function wp_fonts() { // Initialize. $wp_fonts->register_provider( 'local', 'WP_Fonts_Provider_Local' ); add_action( 'wp_head', 'wp_print_fonts', 50 ); - - /* - * For themes without a theme.json, admin printing is initiated by the 'admin_print_styles' hook. - * For themes with theme.json, admin printing is initiated by _wp_get_iframed_editor_assets(). - */ - if ( ! wp_theme_has_theme_json() ) { - add_action( 'admin_print_styles', 'wp_print_fonts', 50 ); - } + add_action( 'admin_print_styles', 'wp_print_fonts', 50 ); } return $wp_fonts; @@ -250,3 +243,17 @@ static function( $mime_types ) { * during the build. See: tools/webpack/blocks.js. */ add_action( 'init', 'WP_Fonts_Resolver::register_fonts_from_theme_json', 21 ); + +add_filter( + 'block_editor_settings_all', + static function( $settings ) { + ob_start(); + wp_print_fonts( true ); + $styles = ob_get_clean(); + + // Add the font-face styles to iframed editor assets. + $settings['__unstableResolvedAssets']['styles'] .= $styles; + return $settings; + }, + 11 +); diff --git a/lib/experimental/fonts/class-wp-font-face-resolver.php b/lib/experimental/fonts/class-wp-font-face-resolver.php new file mode 100644 index 00000000000000..16e74d6051aa74 --- /dev/null +++ b/lib/experimental/fonts/class-wp-font-face-resolver.php @@ -0,0 +1,158 @@ +get_settings(); + + // Bail out early if there are no font settings. + if ( empty( $settings['typography'] ) || empty( $settings['typography']['fontFamilies'] ) ) { + return array(); + } + + return static::parse_settings( $settings ); + } + + /** + * Parse theme.json settings to extract font definitions with variations grouped by font-family. + * + * @since X.X.X + * + * @param array $settings Font settings to parse. + * @return array Returns an array of fonts, grouped by font-family. + */ + private static function parse_settings( array $settings ) { + $fonts = array(); + + foreach ( $settings['typography']['fontFamilies'] as $font_families ) { + foreach ( $font_families as $definition ) { + + // Skip if font-family "name" is not defined. + if ( empty( $definition['name'] ) ) { + continue; + } + + // Skip if "fontFace" is not defined, meaning there are no variations. + if ( empty( $definition['fontFace'] ) ) { + continue; + } + + $font_family = $definition['name']; + + // Prepare the fonts array structure for this font-family. + if ( ! array_key_exists( $font_family, $fonts ) ) { + $fonts[ $font_family ] = array(); + } + + $fonts[ $font_family ] = static::convert_font_face_properties( $definition['fontFace'], $font_family ); + } + } + + return $fonts; + } + + /** + * Converts font-face properties from theme.json format. + * + * @since X.X.X + * + * @param array $font_face_definition The font-face definitions to convert. + * @param string $font_family_property The value to store in the font-face font-family property. + * @return array Converted font-face properties. + */ + private static function convert_font_face_properties( array $font_face_definition, $font_family_property ) { + $converted_font_faces = array(); + + foreach ( $font_face_definition as $font_face ) { + // Add the font-family property to the font-face. + $font_face['font-family'] = $font_family_property; + + // Converts the "file:./" src placeholder into a theme font file URI. + if ( ! empty( $font_face['src'] ) ) { + $font_face['src'] = static::to_theme_file_uri( (array) $font_face['src'] ); + } + + // Convert camelCase properties into kebab-case. + $font_face = static::to_kebab_case( $font_face ); + + $converted_font_faces[] = $font_face; + } + + return $converted_font_faces; + } + + /** + * Converts each 'file:./' placeholder into a URI to the font file in the theme. + * + * The 'file:./' is specified in the theme's `theme.json` as a placeholder to be + * replaced with the URI to the font file's location in the theme. When a "src" + * beings with this placeholder, it is replaced, converting the src into a URI. + * + * @since X.X.X + * + * @param array $src An array of font file sources to process. + * @return array An array of font file src URI(s). + */ + private static function to_theme_file_uri( array $src ) { + $placeholder = 'file:./'; + + foreach ( $src as $src_key => $src_url ) { + // Skip if the src doesn't start with the placeholder, as there's nothing to replace. + if ( ! str_starts_with( $src_url, $placeholder ) ) { + continue; + } + + $src_file = str_replace( $placeholder, '', $src_url ); + $src[ $src_key ] = get_theme_file_uri( $src_file ); + } + + return $src; + } + + /** + * Converts all first dimension keys into kebab-case. + * + * @since X.X.X + * + * @param array $data The array to process. + * @return array Data with first dimension keys converted into kebab-case. + */ + private static function to_kebab_case( array $data ) { + foreach ( $data as $key => $value ) { + $kebab_case = _wp_to_kebab_case( $key ); + $data[ $kebab_case ] = $value; + if ( $kebab_case !== $key ) { + unset( $data[ $key ] ); + } + } + + return $data; + } +} diff --git a/lib/experimental/fonts/class-wp-font-face.php b/lib/experimental/fonts/class-wp-font-face.php new file mode 100644 index 00000000000000..482bf4d42396d3 --- /dev/null +++ b/lib/experimental/fonts/class-wp-font-face.php @@ -0,0 +1,418 @@ + '', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ); + + /** + * Valid font-face property names. + * + * @since X.X.X + * + * @var string[] + */ + private $valid_font_face_properties = array( + 'ascent-override', + 'descent-override', + 'font-display', + 'font-family', + 'font-stretch', + 'font-style', + 'font-weight', + 'font-variant', + 'font-feature-settings', + 'font-variation-settings', + 'line-gap-override', + 'size-adjust', + 'src', + 'unicode-range', + ); + + /** + * Valid font-display values. + * + * @since X.X.X + * + * @var string[] + */ + private $valid_font_display = array( 'auto', 'block', 'fallback', 'swap', 'optional' ); + + /** + * Array of font-face style tag's attribute(s) + * where the key is the attribute name and the + * value is its value. + * + * @since X.X.X + * + * @var string[] + */ + private $style_tag_attrs = array(); + + /** + * Creates and initializes an instance of WP_Font_Face. + * + * @since X.X.X + */ + public function __construct() { + /** + * Filters the font-face property defaults. + * + * @since X.X.X + * + * @param array $defaults { + * An array of required font-face properties and defaults. + * + * @type string $provider The provider ID. Default 'local'. + * @type string $font-family The font-family property. Default empty string. + * @type string $font-style The font-style property. Default 'normal'. + * @type string $font-weight The font-weight property. Default '400'. + * @type string $font-display The font-display property. Default 'fallback'. + * } + */ + $this->font_face_property_defaults = apply_filters( 'wp_font_face_property_defaults', $this->font_face_property_defaults ); + + if ( + function_exists( 'is_admin' ) && ! is_admin() + && + function_exists( 'current_theme_supports' ) && ! current_theme_supports( 'html5', 'style' ) + ) { + $this->style_tag_attrs = array( 'type' => 'text/css' ); + } + } + + /** + * Generates and prints the `@font-face` styles for the given fonts. + * + * @since X.X.X + * + * @param array $fonts The fonts to generate and print @font-face styles. + */ + public function generate_and_print( array $fonts ) { + $fonts = $this->validate_fonts( $fonts ); + + // Bail out if there are no fonts are given to process. + if ( empty( $fonts ) ) { + return; + } + + printf( + $this->get_style_element(), + $this->get_css( $fonts ) + ); + } + + /** + * Validates each of the font-face properties. + * + * @since X.X.X + * + * @param array $fonts The fonts to valid. + * @return array Prepared font-faces organized by provider and font-family. + */ + private function validate_fonts( array $fonts ) { + $validated_fonts = array(); + + foreach ( $fonts as $font_faces ) { + foreach ( $font_faces as $font_face ) { + $font_face = $this->validate_font_face_properties( $font_face ); + // Skip if failed validation. + if ( false === $font_face ) { + continue; + } + + $validated_fonts[] = $font_face; + } + } + + return $validated_fonts; + } + + /** + * Validates each font-face property. + * + * @since X.X.X + * + * @param array $font_face Font face properties to validate. + * @return false|array Validated font-face on success. Else, false. + */ + private function validate_font_face_properties( array $font_face ) { + $font_face = wp_parse_args( $font_face, $this->font_face_property_defaults ); + + // Check the font-family. + if ( empty( $font_face['font-family'] ) || ! is_string( $font_face['font-family'] ) ) { + trigger_error( 'Font font-family must be a non-empty string.' ); + return false; + } + + // Make sure that local fonts have 'src' defined. + if ( empty( $font_face['src'] ) || ( ! is_string( $font_face['src'] ) && ! is_array( $font_face['src'] ) ) ) { + trigger_error( 'Font src must be a non-empty string or an array of strings.' ); + return false; + } + + // Validate the 'src' property. + if ( ! empty( $font_face['src'] ) ) { + foreach ( (array) $font_face['src'] as $src ) { + if ( empty( $src ) || ! is_string( $src ) ) { + trigger_error( 'Each font src must be a non-empty string.' ); + return false; + } + } + } + + // Check the font-weight. + if ( ! is_string( $font_face['font-weight'] ) && ! is_int( $font_face['font-weight'] ) ) { + trigger_error( 'Font font-weight must be a properly formatted string or integer.' ); + return false; + } + + // Check the font-display. + if ( ! in_array( $font_face['font-display'], $this->valid_font_display, true ) ) { + $font_face['font-display'] = $this->font_face_property_defaults['font-display']; + } + + // Remove invalid properties. + foreach ( $font_face as $prop => $value ) { + if ( ! in_array( $prop, $this->valid_font_face_properties, true ) ) { + unset( $font_face[ $prop ] ); + } + } + + return $font_face; + } + + /** + * Gets the `\n"; + } + + /** + * Gets the defined ' + - ( assets?.styles ?? '' ); + const html = ` + + + + + ${ styles } + ${ scripts } + + + + +`; const [ src, cleanup ] = useMemo( () => { const _src = URL.createObjectURL( diff --git a/packages/block-editor/src/components/iframe/use-compatibility-styles.js b/packages/block-editor/src/components/iframe/use-compatibility-styles.js index 4b250175d73d3c..eb738c7ebefdfe 100644 --- a/packages/block-editor/src/components/iframe/use-compatibility-styles.js +++ b/packages/block-editor/src/components/iframe/use-compatibility-styles.js @@ -45,6 +45,11 @@ export function useCompatibilityStyles() { return accumulator; } + // Don't try to add styles without ID. Styles enqueued via the WP dependency system will always have IDs. + if ( ! ownerNode.id ) { + return accumulator; + } + function matchFromRules( _cssRules ) { return Array.from( _cssRules ).find( ( { diff --git a/packages/block-editor/src/components/inner-blocks/README.md b/packages/block-editor/src/components/inner-blocks/README.md index 5ecd9c90898210..0f5d303b8c7917 100644 --- a/packages/block-editor/src/components/inner-blocks/README.md +++ b/packages/block-editor/src/components/inner-blocks/README.md @@ -185,3 +185,13 @@ For example, a button block, deeply nested in several levels of block `X` that u - **Type:** `Array` - **Default:** - `undefined`. Determines which block types should be shown in the block inserter. For example, when inserting a block within the Navigation block we specify `core/navigation-link` and `core/navigation-link/page` as these are the most commonly used inner blocks. `prioritizedInserterBlocks` takes an array of the form {blockName}/{variationName}, where {variationName} is optional. + +### `defaultBlock` + +- **Type:** `Array` +- **Default:** - `undefined`. Determines which block type should be inserted by default and any attributes that should be set by default when the block is inserted. Takes an array in the form of `[ blockname, {blockAttributes} ]`. + +### `directInsert` + +- **Type:** `Boolean` +- **Default:** - `undefined`. Determines whether the default block should be inserted directly into the InnerBlocks area by the block appender. diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js index e1513b9008374c..9e0e4f19cfc7ea 100644 --- a/packages/block-editor/src/components/inner-blocks/index.js +++ b/packages/block-editor/src/components/inner-blocks/index.js @@ -46,6 +46,8 @@ function UncontrolledInnerBlocks( props ) { clientId, allowedBlocks, prioritizedInserterBlocks, + defaultBlock, + directInsert, __experimentalDefaultBlock, __experimentalDirectInsert, template, @@ -64,6 +66,8 @@ function UncontrolledInnerBlocks( props ) { clientId, allowedBlocks, prioritizedInserterBlocks, + defaultBlock, + directInsert, __experimentalDefaultBlock, __experimentalDirectInsert, templateLock, diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index 8af05521e9f036..f07dcf4fc53053 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -73,6 +73,8 @@ function UncontrolledInnerBlocks( props ) { clientId, allowedBlocks, prioritizedInserterBlocks, + defaultBlock, + directInsert, __experimentalDefaultBlock, __experimentalDirectInsert, template, @@ -103,6 +105,8 @@ function UncontrolledInnerBlocks( props ) { clientId, allowedBlocks, prioritizedInserterBlocks, + defaultBlock, + directInsert, __experimentalDefaultBlock, __experimentalDirectInsert, templateLock, diff --git a/packages/block-editor/src/components/inner-blocks/use-nested-settings-update.js b/packages/block-editor/src/components/inner-blocks/use-nested-settings-update.js index 633d6b2701ed65..44f99428a31bf8 100644 --- a/packages/block-editor/src/components/inner-blocks/use-nested-settings-update.js +++ b/packages/block-editor/src/components/inner-blocks/use-nested-settings-update.js @@ -3,6 +3,7 @@ */ import { useLayoutEffect, useMemo } from '@wordpress/element'; import { useSelect, useDispatch, useRegistry } from '@wordpress/data'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies @@ -25,9 +26,13 @@ const pendingSettingsUpdates = new WeakMap(); * @param {string[]} allowedBlocks An array of block names which are permitted * in inner blocks. * @param {string[]} prioritizedInserterBlocks Block names and/or block variations to be prioritized in the inserter, in the format {blockName}/{variationName}. - * @param {?WPDirectInsertBlock} __experimentalDefaultBlock The default block to insert: [ blockName, { blockAttributes } ]. - * @param {?Function|boolean} __experimentalDirectInsert If a default block should be inserted directly by the - * appender. + * @param {?WPDirectInsertBlock} defaultBlock The default block to insert: [ blockName, { blockAttributes } ]. + * @param {?Function|boolean} directInsert If a default block should be inserted directly by the appender. + * + * @param {?WPDirectInsertBlock} __experimentalDefaultBlock A deprecated prop for the default block to insert: [ blockName, { blockAttributes } ]. Use `defaultBlock` instead. + * + * @param {?Function|boolean} __experimentalDirectInsert A deprecated prop for whether a default block should be inserted directly by the appender. Use `directInsert` instead. + * * @param {string} [templateLock] The template lock specified for the inner * blocks component. (e.g. "all") * @param {boolean} captureToolbars Whether or children toolbars should be shown @@ -41,6 +46,8 @@ export default function useNestedSettingsUpdate( clientId, allowedBlocks, prioritizedInserterBlocks, + defaultBlock, + directInsert, __experimentalDefaultBlock, __experimentalDirectInsert, templateLock, @@ -108,11 +115,29 @@ export default function useNestedSettingsUpdate( } if ( __experimentalDefaultBlock !== undefined ) { - newSettings.__experimentalDefaultBlock = __experimentalDefaultBlock; + deprecated( '__experimentalDefaultBlock', { + alternative: 'defaultBlock', + since: '6.3', + version: '6.4', + } ); + newSettings.defaultBlock = __experimentalDefaultBlock; + } + + if ( defaultBlock !== undefined ) { + newSettings.defaultBlock = defaultBlock; } if ( __experimentalDirectInsert !== undefined ) { - newSettings.__experimentalDirectInsert = __experimentalDirectInsert; + deprecated( '__experimentalDirectInsert', { + alternative: 'directInsert', + since: '6.3', + version: '6.4', + } ); + newSettings.directInsert = __experimentalDirectInsert; + } + + if ( directInsert !== undefined ) { + newSettings.directInsert = directInsert; } // Batch updates to block list settings to avoid triggering cascading renders @@ -144,6 +169,8 @@ export default function useNestedSettingsUpdate( _allowedBlocks, _prioritizedInserterBlocks, _templateLock, + defaultBlock, + directInsert, __experimentalDefaultBlock, __experimentalDirectInsert, captureToolbars, diff --git a/packages/block-editor/src/components/inserter/index.js b/packages/block-editor/src/components/inserter/index.js index 9c24497e5a9078..8e2972fbe2bf5e 100644 --- a/packages/block-editor/src/components/inserter/index.js +++ b/packages/block-editor/src/components/inserter/index.js @@ -231,7 +231,7 @@ export const ComposedPrivateInserter = compose( [ getBlockRootClientId, hasInserterItems, getAllowedBlocks, - __experimentalGetDirectInsertBlock, + getDirectInsertBlock, getSettings, } = select( blockEditorStore ); @@ -243,8 +243,7 @@ export const ComposedPrivateInserter = compose( [ const allowedBlocks = getAllowedBlocks( rootClientId ); const directInsertBlock = - shouldDirectInsert && - __experimentalGetDirectInsertBlock( rootClientId ); + shouldDirectInsert && getDirectInsertBlock( rootClientId ); const settings = getSettings(); diff --git a/packages/block-editor/src/components/inserter/index.native.js b/packages/block-editor/src/components/inserter/index.native.js index a3e6981e6ecfc7..6edef19583b3fc 100644 --- a/packages/block-editor/src/components/inserter/index.native.js +++ b/packages/block-editor/src/components/inserter/index.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { AccessibilityInfo, Platform, Text } from 'react-native'; +import { AccessibilityInfo, Platform } from 'react-native'; /** * WordPress dependencies @@ -35,33 +35,17 @@ const VOICE_OVER_ANNOUNCEMENT_DELAY = 1000; const defaultRenderToggle = ( { onToggle, disabled, - style, - containerStyle, + iconStyle, + buttonStyle, onLongPress, - useExpandedMode, } ) => { - // The "expanded mode" refers to the editor's appearance when no blocks - // are currently selected. The "add block" button has a separate style - // for the "expanded mode", which are added via the below "expandedModeViewProps" - // and "expandedModeViewText" variables. - const expandedModeViewProps = useExpandedMode && { - icon: , - customContainerStyles: containerStyle, - fixedRatio: false, - }; - const expandedModeViewText = ( - - { __( 'Add blocks' ) } - - ); - return ( } + icon={ } onClick={ onToggle } extraProps={ { hint: __( 'Double tap to add a block' ), @@ -69,12 +53,12 @@ const defaultRenderToggle = ( { // usually required for components. See: https://github.com/WordPress/gutenberg/pull/18832#issuecomment-561411389. testID: 'add-block-button', onLongPress, + hitSlop: { top: 10, bottom: 10, left: 10, right: 10 }, } } isDisabled={ disabled } - { ...expandedModeViewProps } - > - { useExpandedMode && expandedModeViewText } - + customContainerStyles={ buttonStyle } + fixedRatio={ false } + /> ); }; @@ -249,23 +233,21 @@ export class Inserter extends Component { renderToggle = defaultRenderToggle, getStylesFromColorScheme, showSeparator, - useExpandedMode, } = this.props; if ( showSeparator && isOpen ) { return ; } - const style = useExpandedMode - ? styles[ 'inserter-menu__add-block-button-icon--expanded' ] - : getStylesFromColorScheme( - styles[ 'inserter-menu__add-block-button-icon' ], - styles[ 'inserter-menu__add-block-button-icon--dark' ] - ); - - const containerStyle = getStylesFromColorScheme( + + const buttonStyle = getStylesFromColorScheme( styles[ 'inserter-menu__add-block-button' ], styles[ 'inserter-menu__add-block-button--dark' ] ); + const iconStyle = getStylesFromColorScheme( + styles[ 'inserter-menu__add-block-button-icon' ], + styles[ 'inserter-menu__add-block-button-icon--dark' ] + ); + const onPress = () => { this.setState( { @@ -301,10 +283,9 @@ export class Inserter extends Component { onToggle: onPress, isOpen, disabled, - style, - containerStyle, + iconStyle, + buttonStyle, onLongPress, - useExpandedMode, } ) } ( this.picker = instance ) } diff --git a/packages/block-editor/src/components/inserter/reusable-block-rename-hint.js b/packages/block-editor/src/components/inserter/reusable-block-rename-hint.js new file mode 100644 index 00000000000000..6a3a3d1eec260b --- /dev/null +++ b/packages/block-editor/src/components/inserter/reusable-block-rename-hint.js @@ -0,0 +1,69 @@ +/** + * WordPress dependencies + */ +import { Button } from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { focus } from '@wordpress/dom'; +import { useRef } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { close } from '@wordpress/icons'; +import { store as preferencesStore } from '@wordpress/preferences'; + +const PREFERENCE_NAME = 'isResuableBlocksrRenameHintVisible'; +/* + * This hook was added in 6.3 to help users with the transition from Reusable blocks to Patterns. + * It is only exported for use in the reusable-blocks package as well as block-editor. + * It will be removed in 6.4. and should not be used in any new code. + */ +export function useReusableBlocksRenameHint() { + return useSelect( + ( select ) => + select( preferencesStore ).get( 'core', PREFERENCE_NAME ) ?? true, + [] + ); +} + +/* + * This component was added in 6.3 to help users with the transition from Reusable blocks to Patterns. + * It is only exported for use in the reusable-blocks package as well as block-editor. + * It will be removed in 6.4. and should not be used in any new code. + */ +export default function ReusableBlocksRenameHint() { + const isReusableBlocksRenameHint = useSelect( + ( select ) => + select( preferencesStore ).get( 'core', PREFERENCE_NAME ) ?? true, + [] + ); + + const ref = useRef(); + + const { set: setPreference } = useDispatch( preferencesStore ); + if ( ! isReusableBlocksRenameHint ) { + return null; + } + + return ( +
+
+ { __( + 'Reusable blocks are now synced patterns. A synced pattern will behave in exactly the same way as a reusable block.' + ) } +
+
+ ); +} diff --git a/packages/block-editor/src/components/inserter/reusable-blocks-tab.js b/packages/block-editor/src/components/inserter/reusable-blocks-tab.js index c16d5f1a78e543..08cd8d57ba0d0e 100644 --- a/packages/block-editor/src/components/inserter/reusable-blocks-tab.js +++ b/packages/block-editor/src/components/inserter/reusable-blocks-tab.js @@ -13,6 +13,7 @@ import BlockTypesList from '../block-types-list'; import InserterPanel from './panel'; import InserterNoResults from './no-results'; import useBlockTypesState from './hooks/use-block-types-state'; +import ReusableBlocksRenameHint from './reusable-block-rename-hint'; function ReusableBlocksList( { onHover, onInsert, rootClientId } ) { const [ items, , , onSelectItem ] = useBlockTypesState( @@ -54,6 +55,9 @@ function ReusableBlocksList( { onHover, onInsert, rootClientId } ) { export function ReusableBlocksTab( { rootClientId, onInsert, onHover } ) { return ( <> +
+ +
setIsEditingLink( true ) } hasRichPreviews={ hasRichPreviews } hasUnlinkControl={ shownUnlinkControl } - onRemove={ onRemove } + onRemove={ () => { + onRemove(); + setIsEditingLink( true ); + } } /> ) } diff --git a/packages/block-editor/src/components/link-control/search-item.js b/packages/block-editor/src/components/link-control/search-item.js index 976bb4420cb0cf..e91326b3ba4ee7 100644 --- a/packages/block-editor/src/components/link-control/search-item.js +++ b/packages/block-editor/src/components/link-control/search-item.js @@ -54,7 +54,7 @@ export const LinkControlSearchItem = ( { } ) => { const info = isURL ? __( 'Press ENTER to add this link' ) - : filterURLForDisplay( safeDecodeURI( suggestion?.url ) ); + : filterURLForDisplay( safeDecodeURI( suggestion?.url ), 24 ); return ( ` elements and text nodes. display: inline-block; + width: 100%; + + mark { + font-weight: 600; + color: inherit; + background-color: transparent; + } } .components-menu-item__shortcut { @@ -213,7 +220,7 @@ $preview-image-height: 140px; position: relative; mark { - font-weight: 700; + font-weight: 600; color: inherit; background-color: transparent; } 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 82bb82a0cfc51d..d861938c878735 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -405,6 +405,32 @@ describe( 'Basic rendering', () => { expect( mockOnRemove ).toHaveBeenCalled(); } ); + + it( 'should revert to "editing" mode when onRemove is triggered', async () => { + const user = userEvent.setup(); + const mockOnRemove = jest.fn(); + + render( + + ); + + const unLinkButton = screen.queryByRole( 'button', { + name: 'Unlink', + } ); + expect( unLinkButton ).toBeVisible(); + + await user.click( unLinkButton ); + + expect( mockOnRemove ).toHaveBeenCalled(); + + // Should revert back to editing mode. + expect( + screen.getByRole( 'combobox', { name: 'Link' } ) + ).toBeVisible(); + } ); } ); } ); diff --git a/packages/block-editor/src/components/list-view/use-list-view-client-ids.js b/packages/block-editor/src/components/list-view/use-list-view-client-ids.js index d51412fdf2c3db..8a1ccfcede4c12 100644 --- a/packages/block-editor/src/components/list-view/use-list-view-client-ids.js +++ b/packages/block-editor/src/components/list-view/use-list-view-client-ids.js @@ -16,14 +16,14 @@ export default function useListViewClientIds( { blocks, rootClientId } ) { const { getDraggedBlockClientIds, getSelectedBlockClientIds, - getListViewClientIdsTree, + getEnabledClientIdsTree, } = unlock( select( blockEditorStore ) ); return { selectedClientIds: getSelectedBlockClientIds(), draggedClientIds: getDraggedBlockClientIds(), clientIdsTree: - blocks ?? getListViewClientIdsTree( rootClientId ), + blocks ?? getEnabledClientIdsTree( rootClientId ), }; }, [ blocks, rootClientId ] diff --git a/packages/block-editor/src/components/panel-color-settings/README.md b/packages/block-editor/src/components/panel-color-settings/README.md new file mode 100644 index 00000000000000..94b82eb3869b1a --- /dev/null +++ b/packages/block-editor/src/components/panel-color-settings/README.md @@ -0,0 +1,98 @@ +# PanelColorSettings + +`PanelColorSettings` is a React component that renders a UI for managing various color settings. +It is essentially a wrapper around the `PanelColorGradientSettings` component, but specifically disables the gradient features. + +## Usage + +```jsx +/** + * WordPress dependencies + */ +import { PanelColorSettings } from '@wordpress/block-editor'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +// ... + +const MyPanelColorSettings = () => { + const [ textColor, setTextColor ] = useState( { color: '#000' } ); + const [ backgroundColor, setBackgroundColor ] = useState( { + color: '#fff', + } ); + const [ overlayTextColor, setOverlayTextColor ] = useState( { + color: '#000', + } ); + const [ overlayBackgroundColor, setOverlayBackgroundColor ] = useState( { + color: '#eee', + } ); + + return ( + + ); +}; + +/// ... + +; +``` + +## Props + +The component accepts the following props: + +### colorSettings + +A user-provided set of color settings. + +- Type: `Array` +- Required: No + +Colors settings are provided as an array of objects with the following schema: + +| Property | Description | Type | +| -------- | --------------------------------- | -------- | +| value | The current color of the setting | string | +| onChange | Callback on change of the setting | Function | +| label | Label of the setting | string | + +Additionally, the following `PanelColorGradientSettings` props are supported and directly passed down to the underlying `PanelColorGradientSettings` instance: + +- `className` - added to the underlying `ToolsPanel` instance. +- `colors` - array of colors to be used. +- `gradients` - not recommended to be used since `PanelColorSettings` resets it. +- `disableCustomColors` - whether addition of custom colors is enabled +- `disableCustomGradients` - not recommended to be used since `PanelColorSettings` sets it. +- `children` - displayed below the underlying `PanelColorGradientSettings` instance. +- `settings` - not recommended to be used, since `PanelColorSettings` builds it from the `colorSettings` prop. +- `title` - title of the underlying `ToolsPanel`. +- `showTitle` - whether to show the title of the `ToolsPanel`. +- `__experimentalIsRenderedInSidebar` +- `enableAlpha` - whether to enable setting opacity when specifying a color. + +Please refer to the `PanelColorGradientSettings` component for more information. diff --git a/packages/block-editor/src/components/preview-options/README.md b/packages/block-editor/src/components/preview-options/README.md index 0a2e89a70c7d44..6e9a029fa83a57 100644 --- a/packages/block-editor/src/components/preview-options/README.md +++ b/packages/block-editor/src/components/preview-options/README.md @@ -28,23 +28,24 @@ const MyPreviewOptions = () => ( className="edit-post-post-preview-dropdown" deviceType={ deviceType } setDeviceType={ setPreviewDeviceType } - > - -
- - { __( 'Preview in new tab' ) } - - - } - /> -
-
+ > { ( { onClose } ) => ( + +
+ + { __( 'Preview in new tab' ) } + + + } + onPreview={ onClose } + /> +
+
+ ) } ); ``` diff --git a/packages/block-editor/src/components/preview-options/index.js b/packages/block-editor/src/components/preview-options/index.js index c9f9a6ff782e4f..c22109e7359d1d 100644 --- a/packages/block-editor/src/components/preview-options/index.js +++ b/packages/block-editor/src/components/preview-options/index.js @@ -54,7 +54,7 @@ export default function PreviewOptions( { icon={ deviceIcons[ deviceType.toLowerCase() ] } label={ label || __( 'Preview' ) } > - { () => ( + { ( renderProps ) => ( <> - { children } + { children( renderProps ) } ) } diff --git a/packages/block-editor/src/components/recursion-provider/README.md b/packages/block-editor/src/components/recursion-provider/README.md new file mode 100644 index 00000000000000..4538fd6a7d3507 --- /dev/null +++ b/packages/block-editor/src/components/recursion-provider/README.md @@ -0,0 +1,101 @@ +# RecursionProvider + +According to Gutenberg's block rendering architecture, any block type capable of recursion should be responsible for handling its own infinite loops. + +To help with detecting infinite loops on the client, the `RecursionProvider` component and the `useHasRecursion()` hook are used to identify if a block has already been rendered. + +## Usage + +```jsx +/** + * WordPress dependencies + */ +import { + __experimentalRecursionProvider as RecursionProvider, + __experimentalUseHasRecursion as useHasRecursion, + useBlockProps, + Warning, +} from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; + +export default function MyRecursiveBlockEdit( { attributes: { ref } } ) { + const hasAlreadyRendered = useHasRecursion( ref ); + const blockProps = useBlockProps( { + className: 'my-block__custom-class', + } ); + + if ( hasAlreadyRendered ) { + return ( +
+ + { __( 'Block cannot be rendered inside itself.' ) } + +
+ ); + } + + return ( + + Block editing code here.... + + ); +} + +/// ... + +; +``` + +## Props + +The component accepts the following props: + +### uniqueId + +Any value that acts as a unique identifier for a block instance. + +- Type: `any` +- Required: Yes + +### children + +Components to be rendered as content. + +- Type: `Element` +- Required: Yes. + +### blockName + +Optional block name. + +- Type: `String` +- Required: No +- Default: '' + +# `useHasRecursion()` + +Used in conjunction with `RecursionProvider`, this hook is used to identify if a block has already been rendered. + +## Usage + +For example usage, refer to the example above. + +## Props + +The component accepts the following props: + +### uniqueId + +Any value that acts as a unique identifier for a block instance. + +- Type: `any` +- Required: Yes + +### blockName + +Optional block name. + +- Type: `String` +- Required: No +- Default: '' + diff --git a/packages/block-editor/src/components/rich-text/content.js b/packages/block-editor/src/components/rich-text/content.js index dfd206a1ddb7e9..9762582f86f141 100644 --- a/packages/block-editor/src/components/rich-text/content.js +++ b/packages/block-editor/src/components/rich-text/content.js @@ -2,11 +2,7 @@ * WordPress dependencies */ import { RawHTML } from '@wordpress/element'; -import { - children as childrenSource, - getSaveElement, - __unstableGetBlockProps as getBlockProps, -} from '@wordpress/blocks'; +import { children as childrenSource } from '@wordpress/blocks'; import deprecated from '@wordpress/deprecated'; /** @@ -42,44 +38,3 @@ export const Content = ( { value, tagName: Tag, multiline, ...props } ) => { return content; }; - -Content.__unstableIsRichTextContent = {}; - -function findContent( blocks, richTextValues = [] ) { - if ( ! Array.isArray( blocks ) ) { - blocks = [ blocks ]; - } - - for ( const block of blocks ) { - if ( - block?.type?.__unstableIsRichTextContent === - Content.__unstableIsRichTextContent - ) { - richTextValues.push( block.props.value ); - continue; - } - - if ( block?.props?.children ) { - findContent( block.props.children, richTextValues ); - } - } - - return richTextValues; -} - -function _getSaveElement( { name, attributes, innerBlocks } ) { - return getSaveElement( - name, - attributes, - innerBlocks.map( _getSaveElement ) - ); -} - -export function getRichTextValues( blocks = [] ) { - getBlockProps.skipFilters = true; - const values = findContent( - ( Array.isArray( blocks ) ? blocks : [ blocks ] ).map( _getSaveElement ) - ); - getBlockProps.skipFilters = false; - return values; -} diff --git a/packages/block-editor/src/components/rich-text/get-rich-text-values.js b/packages/block-editor/src/components/rich-text/get-rich-text-values.js new file mode 100644 index 00000000000000..9af2eb054cf6c7 --- /dev/null +++ b/packages/block-editor/src/components/rich-text/get-rich-text-values.js @@ -0,0 +1,105 @@ +/** + * WordPress dependencies + */ +import { RawHTML, StrictMode, Fragment } from '@wordpress/element'; +import { + getSaveElement, + __unstableGetBlockProps as getBlockProps, +} from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import InnerBlocks from '../inner-blocks'; +import { Content } from './content'; + +/* + * This function is similar to `@wordpress/element`'s `renderToString` function, + * except that it does not render the elements to a string, but instead collects + * the values of all rich text `Content` elements. + */ +function addValuesForElement( element, ...args ) { + if ( null === element || undefined === element || false === element ) { + return; + } + + if ( Array.isArray( element ) ) { + return addValuesForElements( element, ...args ); + } + + switch ( typeof element ) { + case 'string': + case 'number': + return; + } + + const { type, props } = element; + + switch ( type ) { + case StrictMode: + case Fragment: + return addValuesForElements( props.children, ...args ); + case RawHTML: + return; + case InnerBlocks.Content: + return addValuesForBlocks( ...args ); + case Content: + const [ values ] = args; + values.push( props.value ); + return; + } + + switch ( typeof type ) { + case 'string': + if ( typeof props.children !== 'undefined' ) { + return addValuesForElements( props.children, ...args ); + } + return; + case 'function': + if ( + type.prototype && + typeof type.prototype.render === 'function' + ) { + return addValuesForElement( + new type( props ).render(), + ...args + ); + } + + return addValuesForElement( type( props ), ...args ); + } +} + +function addValuesForElements( children, ...args ) { + children = Array.isArray( children ) ? children : [ children ]; + + for ( let i = 0; i < children.length; i++ ) { + addValuesForElement( children[ i ], ...args ); + } +} + +function _getSaveElement( name, attributes, innerBlocks ) { + return getSaveElement( + name, + attributes, + innerBlocks.map( ( block ) => + _getSaveElement( block.name, block.attributes, block.innerBlocks ) + ) + ); +} + +function addValuesForBlocks( values, blocks ) { + for ( let i = 0; i < blocks.length; i++ ) { + const { name, attributes, innerBlocks } = blocks[ i ]; + const saveElement = _getSaveElement( name, attributes, innerBlocks ); + addValuesForElement( saveElement, values, innerBlocks ); + } +} + +export function getRichTextValues( blocks = [] ) { + getBlockProps.skipFilters = true; + const values = []; + addValuesForBlocks( values, blocks ); + getBlockProps.skipFilters = false; + return values; +} diff --git a/packages/block-editor/src/components/spacing-sizes-control/hooks/use-spacing-sizes.js b/packages/block-editor/src/components/spacing-sizes-control/hooks/use-spacing-sizes.js index 7fc96caa7fd47a..4a24482f3b1e42 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/hooks/use-spacing-sizes.js +++ b/packages/block-editor/src/components/spacing-sizes-control/hooks/use-spacing-sizes.js @@ -10,7 +10,7 @@ import useSetting from '../../use-setting'; export default function useSpacingSizes() { const spacingSizes = [ - { name: 0, slug: '0', side: 0 }, + { name: 0, slug: '0', size: 0 }, ...( useSetting( 'spacing.spacingSizes' ) || [] ), ]; diff --git a/packages/block-editor/src/components/spacing-sizes-control/input-controls/axial.js b/packages/block-editor/src/components/spacing-sizes-control/input-controls/axial.js index 3ca76d9c8f4827..6811349b74ad74 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/input-controls/axial.js +++ b/packages/block-editor/src/components/spacing-sizes-control/input-controls/axial.js @@ -2,7 +2,12 @@ * Internal dependencies */ import SpacingInputControl from './spacing-input-control'; -import { LABELS, ICONS, hasAxisSupport } from '../utils'; +import { + LABELS, + ICONS, + getPresetValueFromCustomValue, + hasAxisSupport, +} from '../utils'; const groupedSides = [ 'vertical', 'horizontal' ]; @@ -20,7 +25,17 @@ export default function AxialInputControls( { if ( ! onChange ) { return; } - const nextValues = { ...values }; + + // Encode the existing value into the preset value if the passed in value matches the value of one of the spacingSizes. + const nextValues = { + ...Object.keys( values ).reduce( ( acc, key ) => { + acc[ key ] = getPresetValueFromCustomValue( + values[ key ], + spacingSizes + ); + return acc; + }, {} ), + }; if ( side === 'vertical' ) { nextValues.top = next; diff --git a/packages/block-editor/src/components/spacing-sizes-control/input-controls/separated.js b/packages/block-editor/src/components/spacing-sizes-control/input-controls/separated.js index ddfa3ebca8e99b..e9e2f828808549 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/input-controls/separated.js +++ b/packages/block-editor/src/components/spacing-sizes-control/input-controls/separated.js @@ -2,7 +2,12 @@ * Internal dependencies */ import SpacingInputControl from './spacing-input-control'; -import { ALL_SIDES, LABELS, ICONS } from '../utils'; +import { + ALL_SIDES, + LABELS, + ICONS, + getPresetValueFromCustomValue, +} from '../utils'; export default function SeparatedInputControls( { minimumCustomValue, @@ -20,7 +25,17 @@ export default function SeparatedInputControls( { : ALL_SIDES; const createHandleOnChange = ( side ) => ( next ) => { - const nextValues = { ...values }; + // Encode the existing value into the preset value if the passed in value matches the value of one of the spacingSizes. + const nextValues = { + ...Object.keys( values ).reduce( ( acc, key ) => { + acc[ key ] = getPresetValueFromCustomValue( + values[ key ], + spacingSizes + ); + return acc; + }, {} ), + }; + nextValues[ side ] = next; onChange( nextValues ); diff --git a/packages/block-editor/src/components/spacing-sizes-control/input-controls/single.js b/packages/block-editor/src/components/spacing-sizes-control/input-controls/single.js index 2bb0a409da0dd2..df6beb0f8f7b73 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/input-controls/single.js +++ b/packages/block-editor/src/components/spacing-sizes-control/input-controls/single.js @@ -2,7 +2,7 @@ * Internal dependencies */ import SpacingInputControl from './spacing-input-control'; -import { LABELS } from '../utils'; +import { LABELS, getPresetValueFromCustomValue } from '../utils'; export default function SingleInputControl( { minimumCustomValue, @@ -16,7 +16,17 @@ export default function SingleInputControl( { values, } ) { const createHandleOnChange = ( currentSide ) => ( next ) => { - const nextValues = { ...values }; + // Encode the existing value into the preset value if the passed in value matches the value of one of the spacingSizes. + const nextValues = { + ...Object.keys( values ).reduce( ( acc, key ) => { + acc[ key ] = getPresetValueFromCustomValue( + values[ key ], + spacingSizes + ); + return acc; + }, {} ), + }; + nextValues[ currentSide ] = next; onChange( nextValues ); diff --git a/packages/block-editor/src/components/spacing-sizes-control/utils.js b/packages/block-editor/src/components/spacing-sizes-control/utils.js index fe8d89509cdbda..340abb0322e9a2 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/utils.js +++ b/packages/block-editor/src/components/spacing-sizes-control/utils.js @@ -102,7 +102,7 @@ export function getCustomValueFromPreset( value, spacingSizes ) { */ export function getPresetValueFromCustomValue( value, spacingSizes ) { // Return value as-is if it is already a preset; - if ( isValueSpacingPreset( value ) ) { + if ( isValueSpacingPreset( value ) || value === '0' ) { return value; } diff --git a/packages/block-editor/src/components/text-transform-control/README.md b/packages/block-editor/src/components/text-transform-control/README.md new file mode 100644 index 00000000000000..511b73b0ec696b --- /dev/null +++ b/packages/block-editor/src/components/text-transform-control/README.md @@ -0,0 +1,44 @@ +# TextTransformControl + +The `TextTransformControl` component is responsible for rendering a control element that allows users to select and apply text transformation options to blocks or elements in the Gutenberg editor. It provides an intuitive interface for changing the text appearance by applying different transformations such as `none`, `uppercase`, `lowercase`, `capitalize`. + +![TextTransformConrol Element in Inspector Control](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/text-transform-component.png?raw=true) + +## Table of contents + +1. [Development guidelines](#development-guidelines) + +## Development guidelines + +### Usage + +Renders the Text Transform Component with `none`, `uppercase`, `lowercase`, `capitalize` options. + +```jsx +import { TextTransformControl } from '@wordpress/block-editor'; + +const MyTextTransformControlComponent = () => ( + { + setAttributes( { textTransform: value } ); + } } + /> +); +``` + +### Props + +### `value` + +- **Type:** `String` +- **Default:** `none` +- **Options:** `none`, `uppercase`, `lowercase`, `capitalize` + +The current value of the Text Transform setting. You may only choose from the `Options` listed above. + +### `onChange` + +- **Type:** `Function` + +A callback function invoked when the Text Transform value is changed via an interaction with any of the buttons. Called with the Text Transform value (`none`, `uppercase`, `lowercase`, `capitalize`) as the only argument. \ No newline at end of file diff --git a/packages/block-editor/src/components/use-block-display-information/index.js b/packages/block-editor/src/components/use-block-display-information/index.js index 87909cea45f637..1cff9da4bc04a9 100644 --- a/packages/block-editor/src/components/use-block-display-information/index.js +++ b/packages/block-editor/src/components/use-block-display-information/index.js @@ -67,8 +67,11 @@ export default function useBlockDisplayInformation( clientId ) { return useSelect( ( select ) => { if ( ! clientId ) return null; - const { getBlockName, getBlockAttributes } = - select( blockEditorStore ); + const { + getBlockName, + getBlockAttributes, + __experimentalGetReusableBlockTitle, + } = select( blockEditorStore ); const { getBlockType, getActiveBlockVariation } = select( blocksStore ); const blockName = getBlockName( clientId ); @@ -76,12 +79,16 @@ export default function useBlockDisplayInformation( clientId ) { if ( ! blockType ) return null; const attributes = getBlockAttributes( clientId ); const match = getActiveBlockVariation( blockName, attributes ); - const isSynced = - isReusableBlock( blockType ) || isTemplatePart( blockType ); + const isReusable = isReusableBlock( blockType ); + const resusableTitle = isReusable + ? __experimentalGetReusableBlockTitle( attributes.ref ) + : undefined; + const title = resusableTitle || blockType.title; + const isSynced = isReusable || isTemplatePart( blockType ); const positionLabel = getPositionTypeLabel( attributes ); const blockTypeInfo = { isSynced, - title: blockType.title, + title, icon: blockType.icon, description: blockType.description, anchor: attributes?.anchor, diff --git a/packages/block-editor/src/components/use-setting/index.js b/packages/block-editor/src/components/use-setting/index.js index add0b7ff6e08db..c1222c9116ae67 100644 --- a/packages/block-editor/src/components/use-setting/index.js +++ b/packages/block-editor/src/components/use-setting/index.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { get } from 'lodash'; - /** * WordPress dependencies */ @@ -18,6 +13,7 @@ import { applyFilters } from '@wordpress/hooks'; */ import { useBlockEditContext } from '../block-edit'; import { store as blockEditorStore } from '../../store'; +import { getValueFromObjectPath } from '../../utils/object'; const blockedPaths = [ 'color', @@ -165,11 +161,14 @@ export default function useSetting( path ) { candidateClientId ); result = - get( + getValueFromObjectPath( candidateAtts, `settings.blocks.${ blockName }.${ normalizedPath }` ) ?? - get( candidateAtts, `settings.${ normalizedPath }` ); + getValueFromObjectPath( + candidateAtts, + `settings.${ normalizedPath }` + ); if ( result !== undefined ) { // Stop the search for more distant ancestors and move on. break; @@ -183,7 +182,8 @@ export default function useSetting( path ) { const defaultsPath = `__experimentalFeatures.${ normalizedPath }`; const blockPath = `__experimentalFeatures.blocks.${ blockName }.${ normalizedPath }`; result = - get( settings, blockPath ) ?? get( settings, defaultsPath ); + getValueFromObjectPath( settings, blockPath ) ?? + getValueFromObjectPath( settings, defaultsPath ); } // Return if the setting was found in either the block instance or the store. diff --git a/packages/block-editor/src/hooks/margin.js b/packages/block-editor/src/hooks/margin.js index bdf5cabea6a9df..8f723b3f8c97de 100644 --- a/packages/block-editor/src/hooks/margin.js +++ b/packages/block-editor/src/hooks/margin.js @@ -23,7 +23,10 @@ export function MarginVisualizer( { clientId, attributes, forceShow } ) { const margin = attributes?.style?.spacing?.margin; useEffect( () => { - if ( ! blockElement ) { + if ( + ! blockElement || + null === blockElement.ownerDocument.defaultView + ) { return; } diff --git a/packages/block-editor/src/hooks/padding.js b/packages/block-editor/src/hooks/padding.js index f451f2cb4262ea..b6e4e50e30f9cf 100644 --- a/packages/block-editor/src/hooks/padding.js +++ b/packages/block-editor/src/hooks/padding.js @@ -23,7 +23,10 @@ export function PaddingVisualizer( { clientId, attributes, forceShow } ) { const padding = attributes?.style?.spacing?.padding; useEffect( () => { - if ( ! blockElement ) { + if ( + ! blockElement || + null === blockElement.ownerDocument.defaultView + ) { return; } diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index ccc5dcd9ff2e2a..9c6bf957d61c51 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { get } from 'lodash'; - /** * WordPress dependencies */ @@ -14,7 +9,7 @@ import { useMemo } from '@wordpress/element'; */ import { useSetting } from '../components'; import { useSettingsForBlockElement } from '../components/global-styles/hooks'; -import { setImmutably } from '../utils/object'; +import { getValueFromObjectPath, setImmutably } from '../utils/object'; /** * Removed falsy values from nested object. @@ -79,7 +74,10 @@ export function transformStyles( Object.entries( activeSupports ).forEach( ( [ support, isActive ] ) => { if ( isActive ) { migrationPaths[ support ].forEach( ( path ) => { - const styleValue = get( referenceBlockAttributes, path ); + const styleValue = getValueFromObjectPath( + referenceBlockAttributes, + path + ); if ( styleValue ) { returnBlock = { ...returnBlock, diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js index 1200dee367d243..20aeaa2a79040b 100644 --- a/packages/block-editor/src/private-apis.js +++ b/packages/block-editor/src/private-apis.js @@ -4,7 +4,7 @@ import * as globalStyles from './components/global-styles'; import { ExperimentalBlockEditorProvider } from './components/provider'; import { lock } from './lock-unlock'; -import { getRichTextValues } from './components/rich-text/content'; +import { getRichTextValues } from './components/rich-text/get-rich-text-values'; import { kebabCase } from './utils/object'; import ResizableBoxPopover from './components/resizable-box-popover'; import { ComposedPrivateInserter as PrivateInserter } from './components/inserter'; @@ -19,6 +19,10 @@ import { BlockRemovalWarningModal } from './components/block-removal-warning-mod import { useLayoutClasses, useLayoutStyles } from './hooks'; import DimensionsTool from './components/dimensions-tool'; import ResolutionTool from './components/resolution-tool'; +import { + default as ReusableBlocksRenameHint, + useReusableBlocksRenameHint, +} from './components/inserter/reusable-block-rename-hint'; /** * Private @wordpress/block-editor APIs. @@ -43,4 +47,6 @@ lock( privateApis, { useLayoutStyles, DimensionsTool, ResolutionTool, + ReusableBlocksRenameHint, + useReusableBlocksRenameHint, } ); diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 9dd3b24009c512..1d7ec460fe8d9e 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -28,7 +28,6 @@ import { } from '../utils/selection'; import { __experimentalUpdateSettings, - ensureDefaultBlock, privateRemoveBlocks, } from './private-actions'; @@ -403,7 +402,7 @@ export const replaceBlocks = initialPosition, meta, } ); - dispatch( ensureDefaultBlock() ); + dispatch.ensureDefaultBlock(); }; /** diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index 3abc1b0b3bdfd3..e6fb17c5f6e3c7 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -136,21 +136,18 @@ export const isBlockSubtreeDisabled = createSelector( * * @return {Object[]} Tree of block objects with only clientID and innerBlocks set. */ -export const getListViewClientIdsTree = createSelector( +export const getEnabledClientIdsTree = createSelector( ( state, rootClientId = '' ) => { return getBlockOrder( state, rootClientId ).flatMap( ( clientId ) => { if ( getBlockEditingMode( state, clientId ) !== 'disabled' ) { return [ { clientId, - innerBlocks: getListViewClientIdsTree( - state, - clientId - ), + innerBlocks: getEnabledClientIdsTree( state, clientId ), }, ]; } - return getListViewClientIdsTree( state, clientId ); + return getEnabledClientIdsTree( state, clientId ); } ); }, ( state ) => [ diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index b4750e9e261be0..245aaf7adb0fdf 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -1855,7 +1855,6 @@ export function lastBlockInserted( state = {}, action ) { switch ( action.type ) { case 'INSERT_BLOCKS': case 'REPLACE_BLOCKS': - case 'REPLACE_INNER_BLOCKS': if ( ! action.blocks.length ) { return state; } diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index fc314636d11951..3aaec39a986ccd 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -2018,7 +2018,7 @@ export const getInserterItems = createSelector( title: reusableBlock.title.raw, icon, category: 'reusable', - keywords: [], + keywords: [ 'reusable' ], isDisabled: false, utility: 1, // Deprecated. frecency, @@ -2034,11 +2034,13 @@ export const getInserterItems = createSelector( ? getReusableBlocks( state ) .filter( ( reusableBlock ) => - // Filter to either fully synced patterns (sync_status === 'fully'), - // or old school reusable blocks (sync_status === ''). - reusableBlock.meta?.sync_status === 'fully' || - reusableBlock.meta?.sync_status === '' || - ! reusableBlock.meta?.sync_status + // Reusable blocks that are fully synced should have no sync status set + // for backwards compat between patterns and old reusable blocks, but + // some in release 16.1 may have had sync status inadvertantly set to + // 'fully' if created in the site editor. + reusableBlock.wp_pattern_sync_status === 'fully' || + reusableBlock.wp_pattern_sync_status === '' || + ! reusableBlock.wp_pattern_sync_status ) .map( buildReusableBlockInserterItem ) : []; @@ -2212,15 +2214,24 @@ export const getAllowedBlocks = createSelector( return; } - return getBlockTypes().filter( ( blockType ) => + const blockTypes = getBlockTypes().filter( ( blockType ) => canIncludeBlockTypeInInserter( state, blockType, rootClientId ) ); + const hasReusableBlock = + canInsertBlockTypeUnmemoized( state, 'core/block', rootClientId ) && + getReusableBlocks( state ).length > 0; + + return [ + ...blockTypes, + ...( hasReusableBlock ? [ 'core/block' ] : [] ), + ]; }, ( state, rootClientId ) => [ state.blockListSettings[ rootClientId ], state.blocks.byClientId, state.settings.allowedBlockTypes, state.settings.templateLock, + getReusableBlocks( state ), getBlockTypes(), ] ); @@ -2256,15 +2267,15 @@ export const __experimentalGetAllowedBlocks = createSelector( * @property {?Object} attributes Attributes to pass to the newly created block. * @property {?Array} attributesToCopy Attributes to be copied from adjecent blocks when inserted. */ -export const __experimentalGetDirectInsertBlock = createSelector( +export const getDirectInsertBlock = createSelector( ( state, rootClientId = null ) => { if ( ! rootClientId ) { return; } const defaultBlock = - state.blockListSettings[ rootClientId ]?.__experimentalDefaultBlock; + state.blockListSettings[ rootClientId ]?.defaultBlock; const directInsert = - state.blockListSettings[ rootClientId ]?.__experimentalDirectInsert; + state.blockListSettings[ rootClientId ]?.directInsert; if ( ! defaultBlock || ! directInsert ) { return; } @@ -2281,6 +2292,25 @@ export const __experimentalGetDirectInsertBlock = createSelector( ] ); +export const __experimentalGetDirectInsertBlock = createSelector( + ( state, rootClientId = null ) => { + deprecated( + 'wp.data.select( "core/block-editor" ).__experimentalGetDirectInsertBlock', + { + alternative: + 'wp.data.select( "core/block-editor" ).getDirectInsertBlock', + since: '6.3', + version: '6.4', + } + ); + return getDirectInsertBlock( state, rootClientId ); + }, + ( state, rootClientId ) => [ + state.blockListSettings[ rootClientId ], + state.blocks.tree.get( rootClientId ), + ] +); + const checkAllowListRecursive = ( blocks, allowedBlockTypes ) => { if ( typeof allowedBlockTypes === 'boolean' ) { return allowedBlockTypes; @@ -2313,7 +2343,8 @@ function getUnsyncedPatterns( state ) { return reusableBlocks .filter( - ( reusableBlock ) => reusableBlock.meta?.sync_status === 'unsynced' + ( reusableBlock ) => + reusableBlock.wp_pattern_sync_status === 'unsynced' ) .map( ( reusableBlock ) => { return { diff --git a/packages/block-editor/src/store/test/actions.js b/packages/block-editor/src/store/test/actions.js index fc15737eda8d2f..48fc234e0e6c21 100644 --- a/packages/block-editor/src/store/test/actions.js +++ b/packages/block-editor/src/store/test/actions.js @@ -216,6 +216,7 @@ describe( 'actions', () => { getBlockCount: () => 1, }; const dispatch = jest.fn(); + dispatch.ensureDefaultBlock = jest.fn(); replaceBlock( 'chicken', block )( { select, dispatch } ); @@ -281,6 +282,7 @@ describe( 'actions', () => { getBlockCount: () => 1, }; const dispatch = jest.fn(); + dispatch.ensureDefaultBlock = jest.fn(); replaceBlocks( [ 'chicken' ], blocks )( { select, dispatch } ); @@ -314,6 +316,7 @@ describe( 'actions', () => { getBlockCount: () => 1, }; const dispatch = jest.fn(); + dispatch.ensureDefaultBlock = jest.fn(); replaceBlocks( [ 'chicken' ], diff --git a/packages/block-editor/src/store/test/private-selectors.js b/packages/block-editor/src/store/test/private-selectors.js index 30cf702c605263..e826db4a62bb9d 100644 --- a/packages/block-editor/src/store/test/private-selectors.js +++ b/packages/block-editor/src/store/test/private-selectors.js @@ -11,7 +11,7 @@ import { getLastInsertedBlocksClientIds, getBlockEditingMode, isBlockSubtreeDisabled, - getListViewClientIdsTree, + getEnabledClientIdsTree, getEnabledBlockParents, } from '../private-selectors'; @@ -391,7 +391,7 @@ describe( 'private selectors', () => { } ); } ); - describe( 'getListViewClientIdsTree', () => { + describe( 'getEnabledClientIdsTree', () => { const baseState = { settings: {}, blocks: { @@ -462,7 +462,7 @@ describe( 'private selectors', () => { ...baseState, blockEditingModes: new Map( [] ), }; - expect( getListViewClientIdsTree( state ) ).toEqual( [ + expect( getEnabledClientIdsTree( state ) ).toEqual( [ { clientId: '6cf70164-9097-4460-bcbf-200560546988', innerBlocks: [], @@ -500,7 +500,7 @@ describe( 'private selectors', () => { blockEditingModes: new Map( [] ), }; expect( - getListViewClientIdsTree( + getEnabledClientIdsTree( state, 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ) @@ -534,7 +534,7 @@ describe( 'private selectors', () => { [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'contentOnly' ], ] ), }; - expect( getListViewClientIdsTree( state ) ).toEqual( [ + expect( getEnabledClientIdsTree( state ) ).toEqual( [ { clientId: 'b26fc763-417d-4f01-b81c-2ec61e14a972', innerBlocks: [], diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index c06936047f8329..9d886f9aa37bbc 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -3328,25 +3328,32 @@ describe( 'state', () => { expect( state.clientIds ).toEqual( [ clientIdOne, clientIdTwo ] ); } ); - it( 'should return client ids of all blocks when inner blocks are replaced with REPLACE_INNER_BLOCKS', () => { - const clientIdOne = '62bfef6e-d5e9-43ba-b7f9-c77cf354141f'; - const clientIdTwo = '9db792c6-a25a-495d-adbd-97d56a4c4189'; + it( 'should return client ids of the original blocks when inner blocks are replaced with REPLACE_INNER_BLOCKS', () => { + const initialBlocks = deepFreeze( [ + '62bfef6e-d5e9-43ba-b7f9-c77cf354141f', + '9db792c6-a25a-495d-adbd-97d56a4c4189', + ] ); const action = { blocks: [ { - clientId: clientIdOne, + clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', }, { - clientId: clientIdTwo, + clientId: '14501cc2-90a6-4f52-aa36-ab6e896135d1', }, ], type: 'REPLACE_INNER_BLOCKS', }; - const state = lastBlockInserted( {}, action ); + const state = lastBlockInserted( + { + clientIds: initialBlocks, + }, + action + ); - expect( state.clientIds ).toEqual( [ clientIdOne, clientIdTwo ] ); + expect( state.clientIds ).toEqual( initialBlocks ); } ); it( 'should return empty state if last block inserted is called with action RESET_BLOCKS', () => { diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 270bceaad447fb..fc4db2c41f8a3f 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -3351,7 +3351,7 @@ describe( 'selectors', () => { id: 'core/block/1', initialAttributes: { ref: 1 }, isDisabled: false, - keywords: [], + keywords: [ 'reusable' ], name: 'core/block', syncStatus: undefined, title: 'Reusable Block 1', diff --git a/packages/block-editor/src/utils/object.js b/packages/block-editor/src/utils/object.js index a543c1e4f1d301..ed81450e49ab93 100644 --- a/packages/block-editor/src/utils/object.js +++ b/packages/block-editor/src/utils/object.js @@ -56,12 +56,17 @@ export function kebabCase( str ) { /** * Clones an object. + * Arrays are also cloned as arrays. * Non-object values are returned unchanged. * * @param {*} object Object to clone. * @return {*} Cloned object, or original literal non-object value. */ function cloneObject( object ) { + if ( Array.isArray( object ) ) { + return object.map( cloneObject ); + } + if ( object && typeof object === 'object' ) { return { ...Object.fromEntries( @@ -79,7 +84,7 @@ function cloneObject( object ) { /** * Immutably sets a value inside an object. Like `lodash#set`, but returning a * new object. Treats nullish initial values as empty objects. Clones any - * nested objects. + * nested objects. Supports arrays, too. * * @param {Object} object Object to set a value in. * @param {number|string|Array} path Path in the object to modify. @@ -92,7 +97,11 @@ export function setImmutably( object, path, value ) { normalizedPath.reduce( ( acc, key, i ) => { if ( acc[ key ] === undefined ) { - acc[ key ] = {}; + if ( Number.isInteger( path[ i + 1 ] ) ) { + acc[ key ] = []; + } else { + acc[ key ] = {}; + } } if ( i === normalizedPath.length - 1 ) { acc[ key ] = value; @@ -102,3 +111,24 @@ export function setImmutably( object, path, value ) { return newObject; } + +/** + * Helper util to return a value from a certain path of the object. + * Path is specified as either: + * - a string of properties, separated by dots, for example: "x.y". + * - an array of properties, for example `[ 'x', 'y' ]`. + * You can also specify a default value in case the result is nullish. + * + * @param {Object} object Input object. + * @param {string|Array} path Path to the object property. + * @param {*} defaultValue Default value if the value at the specified path is nullish. + * @return {*} Value of the object property at the specified path. + */ +export const getValueFromObjectPath = ( object, path, defaultValue ) => { + const normalizedPath = Array.isArray( path ) ? path : path.split( '.' ); + let value = object; + normalizedPath.forEach( ( fieldName ) => { + value = value?.[ fieldName ]; + } ); + return value ?? defaultValue; +}; diff --git a/packages/block-editor/src/utils/test/object.js b/packages/block-editor/src/utils/test/object.js index def7e5e9c8f057..87f01375df311d 100644 --- a/packages/block-editor/src/utils/test/object.js +++ b/packages/block-editor/src/utils/test/object.js @@ -150,6 +150,22 @@ describe( 'setImmutably', () => { expect( result ).toEqual( { test: 2 } ); } ); + it( 'handles first level arrays properly', () => { + const result = setImmutably( [ 5 ], 0, 6 ); + + expect( result ).toEqual( [ 6 ] ); + } ); + + it( 'handles nested arrays properly', () => { + const result = setImmutably( + [ [ 'foo', [ 'bar' ] ] ], + [ 0, 1, 0 ], + 'baz' + ); + + expect( result ).toEqual( [ [ 'foo', [ 'baz' ] ] ] ); + } ); + describe( 'with array notation access', () => { it( 'assigns values at deeper levels', () => { const result = setImmutably( {}, [ 'foo', 'bar', 'baz' ], 5 ); @@ -236,5 +252,25 @@ describe( 'setImmutably', () => { expect( result.foo.bar ).not.toBe( input.foo.bar ); expect( result.foo.bar.baz ).not.toBe( input.foo.bar.baz ); } ); + + it( 'clones arrays at the first level', () => { + const input = []; + const result = setImmutably( input, 0, 1 ); + + expect( result ).not.toBe( input ); + } ); + + it( 'clones arrays at deeper levels', () => { + const input = [ [ [ [ 'foo', [ 'bar' ] ] ] ] ]; + const result = setImmutably( input, [ 0, 0, 0, 1, 0 ], 'baz' ); + + expect( result ).not.toBe( input ); + expect( result[ 0 ] ).not.toBe( input[ 0 ] ); + expect( result[ 0 ][ 0 ] ).not.toBe( input[ 0 ][ 0 ] ); + expect( result[ 0 ][ 0 ][ 0 ] ).not.toBe( input[ 0 ][ 0 ][ 0 ] ); + expect( result[ 0 ][ 0 ][ 0 ][ 1 ] ).not.toBe( + input[ 0 ][ 0 ][ 0 ][ 1 ] + ); + } ); } ); } ); diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index 12d3e9ce980be5..fa26d4a346cebd 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 8.14.0 (2023-07-05) + ## 8.13.0 (2023-06-23) ## 8.12.0 (2023-06-07) diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 213d6690f7afe8..8799a708620056 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "8.13.0", + "version": "8.14.0", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,7 +31,6 @@ ], "dependencies": { "@babel/runtime": "^7.16.0", - "@preact/signals": "^1.1.3", "@wordpress/a11y": "file:../a11y", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/autop": "file:../autop", @@ -65,13 +64,11 @@ "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", - "deepsignal": "^1.3.0", "escape-html": "^1.0.3", "fast-average-color": "^9.1.1", "fast-deep-equal": "^3.1.3", "memize": "^2.1.0", "micromodal": "^0.4.10", - "preact": "^10.13.2", "remove-accents": "^0.4.2", "uuid": "^8.3.0" }, diff --git a/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap index 97783bf048ec4a..d8da33edc72451 100644 --- a/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap @@ -176,6 +176,7 @@ exports[`Audio block renders audio block error state without crashing 1`] = ` onStartShouldSetResponder={[Function]} placeholder="Add caption" placeholderTextColor="gray" + selectionColor="black" style={ { "backgroundColor": undefined, @@ -401,6 +402,7 @@ exports[`Audio block renders audio file without crashing 1`] = ` onStartShouldSetResponder={[Function]} placeholder="Add caption" placeholderTextColor="gray" + selectionColor="black" style={ { "backgroundColor": undefined, diff --git a/packages/block-library/src/avatar/edit.js b/packages/block-library/src/avatar/edit.js index 5141152189ddf3..8b326f4e72d88a 100644 --- a/packages/block-library/src/avatar/edit.js +++ b/packages/block-library/src/avatar/edit.js @@ -36,6 +36,7 @@ const AvatarInspectorControls = ( { setAttributes( { diff --git a/packages/block-library/src/block/block.json b/packages/block-library/src/block/block.json index 5846e7ead0c9b6..4cb53960725d21 100644 --- a/packages/block-library/src/block/block.json +++ b/packages/block-library/src/block/block.json @@ -4,7 +4,8 @@ "name": "core/block", "title": "Pattern", "category": "reusable", - "description": "Create and save content to reuse across your site. Update the block, and the changes apply everywhere it’s used.", + "description": "Create and save content to reuse across your site. Update the pattern, and the changes apply everywhere it’s used.", + "keywords": [ "reusable" ], "textdomain": "default", "attributes": { "ref": { diff --git a/packages/block-library/src/buttons/edit.js b/packages/block-library/src/buttons/edit.js index 78be70ed9dc354..c901655dd42631 100644 --- a/packages/block-library/src/buttons/edit.js +++ b/packages/block-library/src/buttons/edit.js @@ -51,8 +51,8 @@ function ButtonsEdit( { attributes, className } ) { const innerBlocksProps = useInnerBlocksProps( blockProps, { allowedBlocks: ALLOWED_BLOCKS, - __experimentalDefaultBlock: DEFAULT_BLOCK, - __experimentalDirectInsert: true, + defaultBlock: DEFAULT_BLOCK, + directInsert: true, template: [ [ buttonBlockName, diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index e5ad6c7827d236..46e5012f68a345 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -42,7 +42,6 @@ function ColumnEdit( { hasChildren, isSelected, getStylesFromColorScheme, - isParentSelected, contentStyle, columns, selectedColumnIndex, @@ -140,12 +139,10 @@ function ColumnEdit( { return ( @@ -258,8 +255,6 @@ export default compose( [ const parentId = getBlockRootClientId( clientId ); const hasChildren = !! getBlockCount( clientId ); - const isParentSelected = - selectedBlockClientId && selectedBlockClientId === parentId; const blockOrder = getBlockOrder( parentId ); @@ -271,7 +266,6 @@ export default compose( [ return { hasChildren, - isParentSelected, isSelected, selectedColumnIndex, columns, diff --git a/packages/block-library/src/column/editor.native.scss b/packages/block-library/src/column/editor.native.scss index ef937134a43c3f..f76d85ff44b235 100644 --- a/packages/block-library/src/column/editor.native.scss +++ b/packages/block-library/src/column/editor.native.scss @@ -1,7 +1,3 @@ -.columnPlaceholderNotSelected { - padding-top: $block-selected-to-content; -} - .columnPlaceholder { flex: 1; padding: $block-selected-to-content; diff --git a/packages/block-library/src/columns/edit.js b/packages/block-library/src/columns/edit.js index 682c17c475deec..ec48b50f5b6ad3 100644 --- a/packages/block-library/src/columns/edit.js +++ b/packages/block-library/src/columns/edit.js @@ -123,6 +123,7 @@ function ColumnsEditContainer( { <> diff --git a/packages/block-library/src/comment-author-avatar/edit.js b/packages/block-library/src/comment-author-avatar/edit.js index 9abc9021807f4f..a8c831db620c8b 100644 --- a/packages/block-library/src/comment-author-avatar/edit.js +++ b/packages/block-library/src/comment-author-avatar/edit.js @@ -51,6 +51,7 @@ export default function Edit( { setAttributes( { diff --git a/packages/block-library/src/comment-template/index.php b/packages/block-library/src/comment-template/index.php index 3a553e802de0e7..bb1cfa474e4c36 100644 --- a/packages/block-library/src/comment-template/index.php +++ b/packages/block-library/src/comment-template/index.php @@ -35,8 +35,11 @@ function block_core_comment_template_render_comments( $comments, $block ) { * We set commentId context through the `render_block_context` filter so * that dynamically inserted blocks (at `render_block` filter stage) * will also receive that context. + * + * Use an early priority to so that other 'render_block_context' filters + * have access to the values. */ - add_filter( 'render_block_context', $filter_block_context ); + add_filter( 'render_block_context', $filter_block_context, 1 ); /* * We construct a new WP_Block instance from the parsed block so that @@ -44,7 +47,7 @@ function block_core_comment_template_render_comments( $comments, $block ) { */ $block_content = ( new WP_Block( $block->parsed_block ) )->render( array( 'dynamic' => false ) ); - remove_filter( 'render_block_context', $filter_block_context ); + remove_filter( 'render_block_context', $filter_block_context, 1 ); $children = $comment->get_children(); diff --git a/packages/block-library/src/cover/deprecated.js b/packages/block-library/src/cover/deprecated.js index 7212af5c5cdc91..d1801a11ade9de 100644 --- a/packages/block-library/src/cover/deprecated.js +++ b/packages/block-library/src/cover/deprecated.js @@ -329,6 +329,7 @@ const v11 = { ); }, + migrate: migrateTag, }; // Deprecation for blocks that renders fixed background as backgroud from the main block container. @@ -465,6 +466,7 @@ const v10 = { ); }, + migrate: migrateTag, }; // Deprecation for blocks with `minHeightUnit` set but no `minHeight`. diff --git a/packages/block-library/src/file/block.json b/packages/block-library/src/file/block.json index 6096ba36d2a670..12edc20630d1ed 100644 --- a/packages/block-library/src/file/block.json +++ b/packages/block-library/src/file/block.json @@ -65,8 +65,7 @@ "background": true, "link": true } - }, - "interactivity": true + } }, "viewScript": "file:./view.min.js", "editorStyle": "wp-block-file-editor", diff --git a/packages/block-library/src/file/index.php b/packages/block-library/src/file/index.php index a7011cc9efa348..621f07b5b88eec 100644 --- a/packages/block-library/src/file/index.php +++ b/packages/block-library/src/file/index.php @@ -5,6 +5,24 @@ * @package WordPress */ +if ( gutenberg_should_block_use_interactivity_api( 'core/file' ) ) { + /** + * Replaces view script for the File block with version using Interactivity API. + * + * @param array $metadata Block metadata as read in via block.json. + * + * @return array Filtered block type metadata. + */ + function gutenberg_block_core_file_update_interactive_view_script( $metadata ) { + if ( 'core/file' === $metadata['name'] ) { + $metadata['viewScript'] = array( 'file:./view-interactivity.min.js' ); + $metadata['supports']['interactivity'] = true; + } + return $metadata; + } + add_filter( 'block_type_metadata', 'gutenberg_block_core_file_update_interactive_view_script', 10, 1 ); +} + /** * When the `core/file` block is rendering, check if we need to enqueue the `'wp-block-file-view` script. * @@ -54,7 +72,7 @@ static function ( $matches ) { ); // If it uses the Interactivity API, add the directives. - if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN && $should_load_view_script ) { + if ( gutenberg_should_block_use_interactivity_api( 'core/file' ) && $should_load_view_script ) { $processor = new WP_HTML_Tag_Processor( $content ); $processor->next_tag(); $processor->set_attribute( 'data-wp-interactive', '' ); diff --git a/packages/block-library/src/file/inspector.js b/packages/block-library/src/file/inspector.js index adef947462057c..76ed28d124600e 100644 --- a/packages/block-library/src/file/inspector.js +++ b/packages/block-library/src/file/inspector.js @@ -58,6 +58,7 @@ export default function FileBlockInspector( { { displayPreview && ( + } + label={ __( 'Footnotes' ) } + instructions={ __( + 'Footnotes found in blocks within this document will be displayed here.' + ) } + /> + + ); + } + return ( -
    +
      { footnotes.map( ( { id, content } ) => (
    1. { - const id = createId(); - const newValue = insertObject( - value, - { - type: formatName, - attributes: { - href: '#' + id, - id: `${ id }-link`, - 'data-fn': id, + let id; + if ( isObjectActive ) { + const object = value.replacements[ value.start ]; + id = object?.attributes?.[ 'data-fn' ]; + } else { + id = createId(); + const newValue = insertObject( + value, + { + type: formatName, + attributes: { + 'data-fn': id, + }, + innerHTML: `*`, }, - innerHTML: '*', - }, - value.end, - value.end - ); - newValue.start = newValue.end - 1; - - onChange( newValue ); + value.end, + value.end + ); + newValue.start = newValue.end - 1; + onChange( newValue ); + } // BFS search to find the first footnote block. let fnBlock = null; diff --git a/packages/block-library/src/footnotes/style.scss b/packages/block-library/src/footnotes/style.scss index 4debba0560f173..aa7ab8b6951dd3 100644 --- a/packages/block-library/src/footnotes/style.scss +++ b/packages/block-library/src/footnotes/style.scss @@ -1,17 +1,20 @@ +// These styles are for backwards compatibility with the old footnotes anchors. +// Can be removed in the future. .editor-styles-wrapper, .entry-content { counter-reset: footnotes; } -[data-fn].fn { +a[data-fn].fn { vertical-align: super; font-size: smaller; counter-increment: footnotes; - display: inline-block; + display: inline-flex; + text-decoration: none; text-indent: -9999999px; } -[data-fn].fn::after { +a[data-fn].fn::after { content: "[" counter(footnotes) "]"; text-indent: 0; float: left; diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index acf8d54efb127c..5ae8cd2d6820d2 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -562,7 +562,7 @@ function GalleryEdit( props ) { max={ Math.min( MAX_COLUMNS, images.length ) } { ...MOBILE_CONTROL_PROPS_RANGE_CONTROL } required - size="__unstable-large" + __next40pxDefaultSize /> ) } { // This case is disabled until the issue (https://github.com/WordPress/gutenberg/issues/38444) // is addressed. - it.skip( 'block remains selected after dimissing the media options picker', async () => { + it.skip( 'block remains selected after dismissing the media options picker', async () => { // Initialize with an empty gallery const { getByLabelText, getByText, getByTestId } = await initializeEditor( { @@ -175,7 +175,7 @@ describe( 'Gallery block', () => { expect( getByText( 'Choose images' ) ).toBeVisible(); expect( getByText( 'WordPress Media Library' ) ).toBeVisible(); - // Dimiss the picker + // Dismiss the picker if ( Platform.isIOS ) { fireEvent.press( getByText( 'Cancel' ) ); } else { @@ -511,10 +511,11 @@ describe( 'Gallery block', () => { // Reference: https://github.com/wordpress-mobile/test-cases/blob/trunk/test-cases/gutenberg/gallery.md#tc010 it( 'rearranges gallery items', async () => { // Initialize with a gallery that contains three items - const { galleryBlock } = await initializeWithGalleryBlock( { - numberOfItems: 3, - media, - } ); + const { getByLabelText, galleryBlock } = + await initializeWithGalleryBlock( { + numberOfItems: 3, + media, + } ); // Rearrange items (final disposition will be: Image 3 - Image 1 - Image 2) const galleryItem1 = getGalleryItem( galleryBlock, 1 ); @@ -523,7 +524,7 @@ describe( 'Gallery block', () => { fireEvent.press( galleryItem3 ); await act( () => fireEvent.press( - within( galleryItem3 ).getByLabelText( + getByLabelText( /Move block left from position 3 to position 2/ ) ) @@ -532,7 +533,7 @@ describe( 'Gallery block', () => { fireEvent.press( galleryItem1 ); await act( () => fireEvent.press( - within( galleryItem1 ).getByLabelText( + getByLabelText( /Move block right from position 1 to position 2/ ) ) diff --git a/packages/block-library/src/home-link/index.php b/packages/block-library/src/home-link/index.php index 459979b0d43893..8fb5ed109019d3 100644 --- a/packages/block-library/src/home-link/index.php +++ b/packages/block-library/src/home-link/index.php @@ -98,8 +98,15 @@ function block_core_home_link_build_li_wrapper_attributes( $context ) { $colors['css_classes'], $font_sizes['css_classes'] ); - $classes[] = 'wp-block-navigation-item'; $style_attribute = ( $colors['inline_styles'] . $font_sizes['inline_styles'] ); + $classes[] = 'wp-block-navigation-item'; + + if ( is_front_page() ) { + $classes[] = 'current-menu-item'; + } elseif ( is_home() && ( (int) get_option( 'page_for_posts' ) !== get_queried_object_id() ) ) { + // Edge case where the Reading settings has a posts page set but not a static homepage. + $classes[] = 'current-menu-item'; + } $wrapper_attributes = get_block_wrapper_attributes( array( @@ -124,8 +131,14 @@ function render_block_core_home_link( $attributes, $content, $block ) { if ( empty( $attributes['label'] ) ) { return ''; } + $aria_current = ''; - $aria_current = is_home() || ( is_front_page() && 'page' === get_option( 'show_on_front' ) ) ? ' aria-current="page"' : ''; + if ( is_front_page() ) { + $aria_current = ' aria-current="page"'; + } elseif ( is_home() && ( (int) get_option( 'page_for_posts' ) !== get_queried_object_id() ) ) { + // Edge case where the Reading settings has a posts page set but not a static homepage. + $aria_current = ' aria-current="page"'; + } return sprintf( '
    2. %4$s
    3. ', diff --git a/packages/block-library/src/image/deprecated.js b/packages/block-library/src/image/deprecated.js index 34b4573c1002cb..bdfdca6ee3c4d6 100644 --- a/packages/block-library/src/image/deprecated.js +++ b/packages/block-library/src/image/deprecated.js @@ -6,7 +6,11 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { RichText, useBlockProps } from '@wordpress/block-editor'; +import { + RichText, + useBlockProps, + __experimentalGetElementClassName as getBorderClassesAndStyles, +} from '@wordpress/block-editor'; /** * Deprecation for adding the `wp-image-${id}` class to the image block for @@ -539,4 +543,95 @@ const v5 = { }, }; -export default [ v5, v4, v3, v2, v1 ]; +/** + * Deprecation for adding width and height as style rules on the inner img. + * It also updates the widht and height attributes to be strings instead of numbers. + * + * @see https://github.com/WordPress/gutenberg/pull/31366 + */ +const v6 = { + save( { attributes } ) { + const { + url, + alt, + caption, + align, + href, + rel, + linkClass, + width, + height, + aspectRatio, + scale, + id, + linkTarget, + sizeSlug, + title, + } = attributes; + + const newRel = ! rel ? undefined : rel; + const borderProps = getBorderClassesAndStyles( attributes ); + + const classes = classnames( { + [ `align${ align }` ]: align, + [ `size-${ sizeSlug }` ]: sizeSlug, + 'is-resized': width || height, + 'has-custom-border': + !! borderProps.className || + ( borderProps.style && + Object.keys( borderProps.style ).length > 0 ), + } ); + + const imageClasses = classnames( borderProps.className, { + [ `wp-image-${ id }` ]: !! id, + } ); + + const image = ( + { + ); + + const figure = ( + <> + { href ? ( + + { image } + + ) : ( + image + ) } + { ! RichText.isEmpty( caption ) && ( + + ) } + + ); + + return ( +
      + { figure } +
      + ); + }, +}; + +export default [ v6, v5, v4, v3, v2, v1 ]; diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php index 1e437d860aa097..9c8f66b7ffe185 100644 --- a/packages/block-library/src/image/index.php +++ b/packages/block-library/src/image/index.php @@ -9,11 +9,12 @@ * Renders the `core/image` block on the server, * adding a data-id attribute to the element if core/gallery has added on pre-render. * - * @param array $attributes The block attributes. - * @param string $content The block content. + * @param array $attributes The block attributes. + * @param string $content The block content. + * @param WP_Block $block The block object. * @return string Returns the block content with the data-id attribute added. */ -function render_block_core_image( $attributes, $content ) { +function render_block_core_image( $attributes, $content, $block ) { $processor = new WP_HTML_Tag_Processor( $content ); $processor->next_tag( 'img' ); @@ -30,14 +31,52 @@ function render_block_core_image( $attributes, $content ) { $processor->set_attribute( 'data-id', $attributes['data-id'] ); } + $should_load_view_script = false; + $experiments = get_option( 'gutenberg-experiments' ); + $link_destination = isset( $attributes['linkDestination'] ) ? $attributes['linkDestination'] : 'none'; + // Get the lightbox setting from the block attributes. + if ( isset( $attributes['behaviors']['lightbox'] ) ) { + $lightbox_settings = $attributes['behaviors']['lightbox']; + // If the lightbox setting is not set in the block attributes, get it from the theme.json file. + } else { + $theme_data = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data()->get_data(); + if ( isset( $theme_data['behaviors']['blocks'][ $block->name ]['lightbox'] ) ) { + $lightbox_settings = $theme_data['behaviors']['blocks'][ $block->name ]['lightbox']; + } else { + $lightbox_settings = null; + } + } + + // If the lightbox is enabled, the image is not linked, and the Interactivity API is enabled, load the view script. + if ( isset( $lightbox_settings['enabled'] ) && + true === $lightbox_settings['enabled'] && + 'none' === $link_destination && + ! empty( $experiments['gutenberg-interactivity-api-core-blocks'] ) + ) { + $should_load_view_script = true; + } + + $view_js_file = 'wp-block-image-view'; + if ( ! wp_script_is( $view_js_file ) ) { + $script_handles = $block->block_type->view_script_handles; + + // If the script is not needed, and it is still in the `view_script_handles`, remove it. + if ( ! $should_load_view_script && in_array( $view_js_file, $script_handles, true ) ) { + $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file ) ); + } + // If the script is needed, but it was previously removed, add it again. + if ( $should_load_view_script && ! in_array( $view_js_file, $script_handles, true ) ) { + $block->block_type->view_script_handles = array_merge( $script_handles, array( $view_js_file ) ); + } + } + return $processor->get_updated_html(); } -/** - * Registers the `core/image` block on server. - */ + /** + * Registers the `core/image` block on server. + */ function register_block_core_image() { - register_block_type_from_metadata( __DIR__ . '/image', array( @@ -45,4 +84,4 @@ function register_block_core_image() { ) ); } -add_action( 'init', 'register_block_core_image' ); + add_action( 'init', 'register_block_core_image' ); diff --git a/packages/block-library/src/image/save.js b/packages/block-library/src/image/save.js index 95e8803dd67858..6fa8c6b2342f32 100644 --- a/packages/block-library/src/image/save.js +++ b/packages/block-library/src/image/save.js @@ -58,6 +58,8 @@ export default function save( { attributes } ) { ...borderProps.style, aspectRatio, objectFit: scale, + width, + height, } } width={ width } height={ height } diff --git a/packages/block-library/src/latest-comments/edit.js b/packages/block-library/src/latest-comments/edit.js index 59258f3c0b090c..85e66cf2e9dc60 100644 --- a/packages/block-library/src/latest-comments/edit.js +++ b/packages/block-library/src/latest-comments/edit.js @@ -68,6 +68,7 @@ export default function LatestComments( { attributes, setAttributes } ) { /> diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index 8fcc2cc5b1ddda..7aaf1b3ecf0eda 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -230,6 +230,7 @@ export default function LatestPostsEdit( { attributes, setAttributes } ) { displayPostContentRadio === 'excerpt' && ( @@ -359,6 +360,7 @@ export default function LatestPostsEdit( { attributes, setAttributes } ) { { postLayout === 'grid' && ( diff --git a/packages/block-library/src/list/edit.js b/packages/block-library/src/list/edit.js index 24d5ead74c47d4..7c8c15e05fe875 100644 --- a/packages/block-library/src/list/edit.js +++ b/packages/block-library/src/list/edit.js @@ -177,10 +177,12 @@ export default function Edit( { attributes, setAttributes, clientId, style } ) { { controls } { ordered && ( ) } diff --git a/packages/block-library/src/list/test/edit.native.js b/packages/block-library/src/list/test/edit.native.js index ef31285100196e..33af381825c16e 100644 --- a/packages/block-library/src/list/test/edit.native.js +++ b/packages/block-library/src/list/test/edit.native.js @@ -20,7 +20,7 @@ import { */ import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; import { registerCoreBlocks } from '@wordpress/block-library'; -import { BACKSPACE } from '@wordpress/keycodes'; +import { BACKSPACE, ENTER } from '@wordpress/keycodes'; describe( 'List block', () => { beforeAll( () => { @@ -339,34 +339,125 @@ describe( 'List block', () => { expect( getEditorHtml() ).toMatchSnapshot(); } ); - it( 'merges with other lists', async () => { + it( 'splits empty list items into paragraphs', async () => { + // Arrange const initialHtml = `
        -
      • One
      - -
        +
      • One
      • +
      • Two
      `; + const screen = await initializeEditor( { initialHtml } ); - const screen = await initializeEditor( { - initialHtml, - } ); - - // Select List block - const [ listBlock ] = screen.getAllByLabelText( /List Block\. Row 2/ ); + // Act + const listBlock = screen.getByLabelText( /List Block\. Row 1/ ); fireEvent.press( listBlock ); await triggerBlockListLayout( listBlock ); + const listItemField = screen.getByLabelText( /Text input. .*One.*/ ); + selectRangeInRichText( listItemField, 3 ); + fireEvent( listItemField, 'onKeyDown', { + nativeEvent: {}, + preventDefault() {}, + keyCode: ENTER, + } ); + const listItemField2 = screen.getByLabelText( /Text input. Empty/ ); + fireEvent( listItemField2, 'onKeyDown', { + nativeEvent: {}, + preventDefault() {}, + keyCode: ENTER, + } ); - // Select List Item block - const [ listItemBlock ] = within( listBlock ).getAllByLabelText( - /List item Block\. Row 1/ - ); - fireEvent.press( listItemBlock ); + // Assert + expect( getEditorHtml() ).toMatchInlineSnapshot( ` + " +
        +
      • One
      • +
      + - // With cursor positioned at the beginning of the first List Item, press - // backward delete - const listItemField = - within( listItemBlock ).getByLabelText( /Text input. .*Two.*/ ); + +

      + + + +
        +
      • Two
      • +
      + " + ` ); + } ); + + it( 'merges paragraphs into list items', async () => { + const initialHtml = ` +
        +
      • One
      • +
      + + + +

      Two

      + + + +
        +
      • Three
      • +
      + `; + const screen = await initializeEditor( { initialHtml } ); + + // Act + const paragraphField = screen.getByLabelText( /Text input. .*Two.*/ ); + selectRangeInRichText( paragraphField, 0 ); + fireEvent( paragraphField, 'onKeyDown', { + nativeEvent: {}, + preventDefault() {}, + keyCode: BACKSPACE, + } ); + + // Assert + expect( getEditorHtml() ).toMatchInlineSnapshot( ` + " +
        +
      • One
      • + + + +
      • Two
      • +
      + + + +
        +
      • Three
      • +
      + " + ` ); + } ); + + it( 'merges lists into lists', async () => { + // Arrange + const initialHtml = ` +
        +
      • One
      • + + + +
      • Two
      • +
      + + + +
        +
      • Three
      • +
      + `; + const screen = await initializeEditor( { initialHtml } ); + + // Act + const listBlock = screen.getByLabelText( /List Block\. Row 2/ ); + fireEvent.press( listBlock ); + await triggerBlockListLayout( listBlock ); + const listItemField = screen.getByLabelText( /Text input\..*Three/ ); selectRangeInRichText( listItemField, 0 ); fireEvent( listItemField, 'onKeyDown', { nativeEvent: {}, @@ -374,17 +465,22 @@ describe( 'List block', () => { keyCode: BACKSPACE, } ); + // Assert expect( getEditorHtml() ).toMatchInlineSnapshot( ` - " -
        -
      • One
      • - - - -
      • Two
      • -
      - " - ` ); + " +
        +
      • One
      • + + + +
      • Two
      • + + + +
      • Three
      • +
      + " + ` ); } ); it( 'unwraps first item when attempting to merge with non-list block', async () => { @@ -491,16 +587,16 @@ describe( 'List block', () => { "

      A quick brown fox.

      - +

      One

      - +
      • Two
      • - +
      • Three
      diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index b91015313f4039..d0d739575963a0 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -181,6 +181,10 @@ export default function NavigationLinkEdit( { const itemLabelPlaceholder = __( 'Add label…' ); const ref = useRef(); + // Change the label using inspector causes rich text to change focus on firefox. + // This is a workaround to keep the focus on the label field when label filed is focused we don't render the rich text. + const [ isLabelFieldFocused, setIsLabelFieldFocused ] = useState( false ); + const { innerBlocks, isAtMaxNesting, @@ -368,8 +372,8 @@ export default function NavigationLinkEdit( { }, { allowedBlocks: ALLOWED_BLOCKS, - __experimentalDefaultBlock: DEFAULT_BLOCK, - __experimentalDirectInsert: true, + defaultBlock: DEFAULT_BLOCK, + directInsert: true, renderAppender: false, } ); @@ -424,6 +428,8 @@ export default function NavigationLinkEdit( { } } label={ __( 'Label' ) } autoComplete="off" + onFocus={ () => setIsLabelFieldFocused( true ) } + onBlur={ () => setIsLabelFieldFocused( false ) } /> ) : ( <> - { ! isInvalid && ! isDraft && ( - <> - - setAttributes( { - label: labelValue, - } ) - } - onMerge={ mergeBlocks } - onReplace={ onReplace } - __unstableOnSplitAtEnd={ () => - insertBlocksAfter( - createBlock( - 'core/navigation-link' + { ! isInvalid && + ! isDraft && + ! isLabelFieldFocused && ( + <> + + setAttributes( { + label: labelValue, + } ) + } + onMerge={ mergeBlocks } + onReplace={ onReplace } + __unstableOnSplitAtEnd={ () => + insertBlocksAfter( + createBlock( + 'core/navigation-link' + ) ) - ) - } - aria-label={ __( - 'Navigation link text' - ) } - placeholder={ itemLabelPlaceholder } - withoutInteractiveFormatting - allowedFormats={ [ - 'core/bold', - 'core/italic', - 'core/image', - 'core/strikethrough', - ] } - onClick={ () => { - if ( ! url ) { - setIsLinkOpen( true ); } - } } - /> - { description && ( - - { description } - - ) } - - ) } - { ( isInvalid || isDraft ) && ( + aria-label={ __( + 'Navigation link text' + ) } + placeholder={ itemLabelPlaceholder } + withoutInteractiveFormatting + allowedFormats={ [ + 'core/bold', + 'core/italic', + 'core/image', + 'core/strikethrough', + ] } + onClick={ () => { + if ( ! url ) { + setIsLinkOpen( true ); + } + } } + /> + { description && ( + + { description } + + ) } + + ) } + { ( isInvalid || + isDraft || + isLabelFieldFocused ) && (
      diff --git a/packages/block-library/src/navigation-submenu/edit.js b/packages/block-library/src/navigation-submenu/edit.js index 7707a6442111e5..ad907a0cb92cbb 100644 --- a/packages/block-library/src/navigation-submenu/edit.js +++ b/packages/block-library/src/navigation-submenu/edit.js @@ -320,8 +320,8 @@ export default function NavigationSubmenuEdit( { getNavigationChildBlockProps( innerBlocksColors ); const innerBlocksProps = useInnerBlocksProps( navigationChildBlockProps, { allowedBlocks, - __experimentalDefaultBlock: DEFAULT_BLOCK, - __experimentalDirectInsert: true, + defaultBlock: DEFAULT_BLOCK, + directInsert: true, // Ensure block toolbar is not too far removed from item // being edited. diff --git a/packages/block-library/src/navigation/block.json b/packages/block-library/src/navigation/block.json index 7896ea147699f7..e45d0535367786 100644 --- a/packages/block-library/src/navigation/block.json +++ b/packages/block-library/src/navigation/block.json @@ -131,10 +131,9 @@ } } } - }, - "interactivity": true + } }, - "viewScript": "file:./view.min.js", + "viewScript": [ "file:./view.min.js", "file:./view-modal.min.js" ], "editorStyle": "wp-block-navigation-editor", "style": "wp-block-navigation" } diff --git a/packages/block-library/src/navigation/constants.js b/packages/block-library/src/navigation/constants.js index 5e6b26ae40b0f4..07d71d50dd98ce 100644 --- a/packages/block-library/src/navigation/constants.js +++ b/packages/block-library/src/navigation/constants.js @@ -20,13 +20,19 @@ export const PRIORITIZED_INSERTER_BLOCKS = [ 'core/navigation-link', ]; +// These parameters must be kept aligned with those in +// lib/compat/wordpress-6.3/navigation-block-preloading.php +// and +// edit-site/src/components/sidebar-navigation-screen-navigation-menus/constants.js +export const PRELOADED_NAVIGATION_MENUS_QUERY = { + per_page: 100, + status: [ 'publish', 'draft' ], + order: 'desc', + orderby: 'date', +}; + export const SELECT_NAVIGATION_MENUS_ARGS = [ 'postType', 'wp_navigation', - { - per_page: 100, - status: [ 'publish', 'draft' ], - order: 'desc', - orderby: 'date', - }, + PRELOADED_NAVIGATION_MENUS_QUERY, ]; diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index 7ec990d5b7389c..6f629c20c0cfd9 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -38,6 +38,7 @@ import { __experimentalToggleGroupControlOption as ToggleGroupControlOption, Button, Spinner, + Notice, } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { speak } from '@wordpress/a11y'; @@ -196,7 +197,7 @@ function Navigation( { convert: convertClassicMenu, status: classicMenuConversionStatus, error: classicMenuConversionError, - } = useConvertClassicToBlockMenu( clientId ); + } = useConvertClassicToBlockMenu( createNavigationMenu ); const isConvertingClassicMenu = classicMenuConversionStatus === CLASSIC_MENU_CONVERSION_PENDING; @@ -485,6 +486,21 @@ function Navigation( { { open: overlayMenuPreview } ); + const submenuAccessibilityNotice = + ! showSubmenuIcon && ! openSubmenusOnClick + ? __( + 'The current menu options offer reduced accessibility for users and are not recommended. Enabling either "Open on Click" or "Show arrow" offers enhanced accessibility by allowing keyboard users to browse submenus selectively.' + ) + : ''; + + const isFirstRender = useRef( true ); // Don't speak on first render. + useEffect( () => { + if ( ! isFirstRender.current && submenuAccessibilityNotice ) { + speak( submenuAccessibilityNotice ); + } + isFirstRender.current = false; + }, [ submenuAccessibilityNotice ] ); + const colorGradientSettings = useMultipleOriginColorsAndGradients(); const stylingInspectorControls = ( <> @@ -578,6 +594,18 @@ function Navigation( { disabled={ attributes.openSubmenusOnClick } label={ __( 'Show arrow' ) } /> + + { submenuAccessibilityNotice && ( +
      + + { submenuAccessibilityNotice } + +
      + ) } ) } diff --git a/packages/block-library/src/navigation/edit/inner-blocks.js b/packages/block-library/src/navigation/edit/inner-blocks.js index 669703f002dbb2..373acd1f52dfa8 100644 --- a/packages/block-library/src/navigation/edit/inner-blocks.js +++ b/packages/block-library/src/navigation/edit/inner-blocks.js @@ -98,8 +98,8 @@ export default function NavigationInnerBlocks( { onChange, allowedBlocks: ALLOWED_BLOCKS, prioritizedInserterBlocks: PRIORITIZED_INSERTER_BLOCKS, - __experimentalDefaultBlock: DEFAULT_BLOCK, - __experimentalDirectInsert: shouldDirectInsert, + defaultBlock: DEFAULT_BLOCK, + directInsert: shouldDirectInsert, orientation, templateLock, diff --git a/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js b/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js index 52f6d8134042de..e2ad08c2a99ab1 100644 --- a/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js +++ b/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js @@ -68,8 +68,8 @@ export default function UnsavedInnerBlocks( { { renderAppender: hasSelection ? undefined : false, allowedBlocks: ALLOWED_BLOCKS, - __experimentalDefaultBlock: DEFAULT_BLOCK, - __experimentalDirectInsert: shouldDirectInsert, + defaultBlock: DEFAULT_BLOCK, + directInsert: shouldDirectInsert, } ); diff --git a/packages/block-library/src/navigation/edit/use-convert-classic-menu-to-block-menu.js b/packages/block-library/src/navigation/edit/use-convert-classic-menu-to-block-menu.js index 70f9a6ff4bfeab..300672fa91e8ad 100644 --- a/packages/block-library/src/navigation/edit/use-convert-classic-menu-to-block-menu.js +++ b/packages/block-library/src/navigation/edit/use-convert-classic-menu-to-block-menu.js @@ -9,7 +9,6 @@ import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies */ -import useCreateNavigationMenu from './use-create-navigation-menu'; import menuItemsToBlocks from '../menu-items-to-blocks'; export const CLASSIC_MENU_CONVERSION_SUCCESS = 'success'; @@ -21,15 +20,10 @@ export const CLASSIC_MENU_CONVERSION_IDLE = 'idle'; // do not import the same classic menu twice. let classicMenuBeingConvertedId = null; -function useConvertClassicToBlockMenu( clientId ) { - /* - * The wp_navigation post is created as a draft so the changes on the frontend and - * the site editor are not permanent without a save interaction done by the user. - */ - const { create: createNavigationMenu } = useCreateNavigationMenu( - clientId, - 'draft' - ); +function useConvertClassicToBlockMenu( + createNavigationMenu, + { throwOnError = false } = {} +) { const registry = useRegistry(); const { editEntityRecord } = useDispatch( coreStore ); @@ -158,19 +152,21 @@ function useConvertClassicToBlockMenu( clientId ) { classicMenuBeingConvertedId = null; // Rethrow error for debugging. - throw new Error( - sprintf( - // translators: %s: the name of a menu (e.g. Header navigation). - __( `Unable to create Navigation Menu "%s".` ), - menuName - ), - { - cause: err, - } - ); + if ( throwOnError ) { + throw new Error( + sprintf( + // translators: %s: the name of a menu (e.g. Header navigation). + __( `Unable to create Navigation Menu "%s".` ), + menuName + ), + { + cause: err, + } + ); + } } ); }, - [ convertClassicMenuToBlockMenu ] + [ convertClassicMenuToBlockMenu, throwOnError ] ); return { diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index e86561e8f4dbd0..79988c3ee350bb 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -65,6 +65,9 @@ function block_core_navigation_sort_menu_items_by_parent_id( $menu_items ) { return $menu_items_by_parent_id; } +} + +if ( gutenberg_should_block_use_interactivity_api( 'core/navigation' ) ) { /** * Add Interactivity API directives to the navigation-submenu and page-list blocks markup using the Tag Processor @@ -103,7 +106,7 @@ function block_core_navigation_add_directives_to_submenu( $w, $block_attributes ) ) { // Add directives to the parent `
    4. `. $w->set_attribute( 'data-wp-interactive', true ); - $w->set_attribute( 'data-wp-context', '{ "core": { "navigation": { "isMenuOpen": { "click": false, "hover": false }, "overlay": false } } }' ); + $w->set_attribute( 'data-wp-context', '{ "core": { "navigation": { "submenuOpenedBy": {}, "type": "submenu" } } }' ); $w->set_attribute( 'data-wp-effect', 'effects.core.navigation.initMenu' ); $w->set_attribute( 'data-wp-on--focusout', 'actions.core.navigation.handleMenuFocusout' ); $w->set_attribute( 'data-wp-on--keydown', 'actions.core.navigation.handleMenuKeydown' ); @@ -123,13 +126,38 @@ function block_core_navigation_add_directives_to_submenu( $w, $block_attributes $w->set_attribute( 'data-wp-bind--aria-expanded', 'selectors.core.navigation.isMenuOpen' ); }; + // Add directives to the submenu. + if ( $w->next_tag( + array( + 'tag_name' => 'UL', + 'class_name' => 'wp-block-navigation__submenu-container', + ) + ) ) { + $w->set_attribute( 'data-wp-on--focusin', 'actions.core.navigation.openMenuOnFocus' ); + } + // Iterate through subitems if exist. block_core_navigation_add_directives_to_submenu( $w, $block_attributes ); } return $w->get_updated_html(); }; -} + /** + * Replaces view script for the Navigation block with version using Interactivity API. + * + * @param array $metadata Block metadata as read in via block.json. + * + * @return array Filtered block type metadata. + */ + function gutenberg_block_core_navigation_update_interactive_view_script( $metadata ) { + if ( 'core/navigation' === $metadata['name'] ) { + $metadata['viewScript'] = array( 'file:./view-interactivity.min.js' ); + $metadata['supports']['interactivity'] = true; + } + return $metadata; + } + add_filter( 'block_type_metadata', 'gutenberg_block_core_navigation_update_interactive_view_script', 10, 1 ); +} /** @@ -660,7 +688,7 @@ function render_block_core_navigation( $attributes, $content, $block ) { } // Add directives to the submenu if needed. - if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN && $has_submenus && $should_load_view_script ) { + if ( gutenberg_should_block_use_interactivity_api( 'core/navigation' ) && $has_submenus && $should_load_view_script ) { $w = new WP_HTML_Tag_Processor( $inner_blocks_html ); $inner_blocks_html = block_core_navigation_add_directives_to_submenu( $w, $attributes ); } @@ -708,10 +736,10 @@ function render_block_core_navigation( $attributes, $content, $block ) { $responsive_container_directives = ''; $responsive_dialog_directives = ''; $close_button_directives = ''; - if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN && $should_load_view_script ) { + if ( gutenberg_should_block_use_interactivity_api( 'core/navigation' ) && $should_load_view_script ) { $nav_element_directives = ' data-wp-interactive - data-wp-context=\'{ "core": { "navigation": { "isMenuOpen": { "click": false, "hover": false }, "overlay": true, "roleAttribute": "" } } }\' + data-wp-context=\'{ "core": { "navigation": { "overlayOpenedBy": {}, "type": "overlay", "roleAttribute": "" } } }\' '; $open_button_directives = ' data-wp-on--click="actions.core.navigation.openMenuOnClick" diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index b0c8748075bdd7..180b40b43daca1 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -31,6 +31,7 @@ $navigation-icon-size: 24px; // Menu item container. .wp-block-navigation-item { + background-color: inherit; display: flex; align-items: center; position: relative; @@ -398,15 +399,26 @@ button.wp-block-navigation-item__content { } // Default background and font color. -.wp-block-navigation:not(.has-background) { +.wp-block-navigation:not(.has-background) .wp-block-navigation__submenu-container { + // Set a background color for submenus so that they're not transparent. + // NOTE TO DEVS - if refactoring this code, please double-check that + // submenus have a default background color, this feature has regressed + // several times, so care needs to be taken. + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.15); +} + +// If we do have a background color selected, inherit it from the navigation block +.wp-block-navigation.has-background { .wp-block-navigation__submenu-container { - // Set a background color for submenus so that they're not transparent. - // NOTE TO DEVS - if refactoring this code, please double-check that - // submenus have a default background color, this feature has regressed - // several times, so care needs to be taken. - background-color: #fff; + background-color: inherit; + } +} + +.wp-block-navigation:not(.has-text-color) { + .wp-block-navigation__submenu-container { + // Set a default color for submenu text if none is selected color: #000; - border: 1px solid rgba(0, 0, 0, 0.15); } } @@ -458,7 +470,8 @@ button.wp-block-navigation-item__content { right: 0; bottom: 0; - .wp-block-navigation-link a { + // Low specificity so that themes can override. + & :where(.wp-block-navigation-item a) { color: inherit; } @@ -579,6 +592,7 @@ button.wp-block-navigation-item__content { // Remove background colors for items inside the overlay menu. // Has to be !important to override global styles. .wp-block-navigation-item .wp-block-navigation__submenu-container, + .wp-block-navigation__container, .wp-block-navigation-item, .wp-block-page-list { color: inherit !important; @@ -620,9 +634,14 @@ button.wp-block-navigation-item__content { .wp-block-navigation:not(.has-background) .wp-block-navigation__responsive-container.is-menu-open { background-color: #fff; +} + +.wp-block-navigation:not(.has-text-color) +.wp-block-navigation__responsive-container.is-menu-open { color: #000; } + // Overlay menu toggle button label .wp-block-navigation__toggle_button_label { font-size: 1rem; diff --git a/packages/block-library/src/navigation/use-navigation-menu.js b/packages/block-library/src/navigation/use-navigation-menu.js index 30e5cdd99c0924..02df0ba2831dcc 100644 --- a/packages/block-library/src/navigation/use-navigation-menu.js +++ b/packages/block-library/src/navigation/use-navigation-menu.js @@ -4,85 +4,61 @@ import { store as coreStore, useResourcePermissions, + useEntityRecords, } from '@wordpress/core-data'; import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ -import { SELECT_NAVIGATION_MENUS_ARGS } from './constants'; +import { PRELOADED_NAVIGATION_MENUS_QUERY } from './constants'; export default function useNavigationMenu( ref ) { const permissions = useResourcePermissions( 'navigation', ref ); - return useSelect( + const { + navigationMenu, + isNavigationMenuResolved, + isNavigationMenuMissing, + } = useSelect( ( select ) => { - const { - canCreate, - canUpdate, - canDelete, - isResolving, - hasResolved, - } = permissions; - - const { - navigationMenus, - isResolvingNavigationMenus, - hasResolvedNavigationMenus, - } = selectNavigationMenus( select ); - - const { - navigationMenu, - isNavigationMenuResolved, - isNavigationMenuMissing, - } = selectExistingMenu( select, ref ); - - return { - navigationMenus, - isResolvingNavigationMenus, - hasResolvedNavigationMenus, - - navigationMenu, - isNavigationMenuResolved, - isNavigationMenuMissing, - - canSwitchNavigationMenu: ref - ? navigationMenus?.length > 1 - : navigationMenus?.length > 0, - - canUserCreateNavigationMenu: canCreate, - isResolvingCanUserCreateNavigationMenu: isResolving, - hasResolvedCanUserCreateNavigationMenu: hasResolved, - - canUserUpdateNavigationMenu: canUpdate, - hasResolvedCanUserUpdateNavigationMenu: ref - ? hasResolved - : undefined, - - canUserDeleteNavigationMenu: canDelete, - hasResolvedCanUserDeleteNavigationMenu: ref - ? hasResolved - : undefined, - }; + return selectExistingMenu( select, ref ); }, - [ ref, permissions ] + [ ref ] ); -} -function selectNavigationMenus( select ) { - const { getEntityRecords, hasFinishedResolution, isResolving } = - select( coreStore ); + const { canCreate, canUpdate, canDelete, isResolving, hasResolved } = + permissions; + + const { + records: navigationMenus, + isResolving: isResolvingNavigationMenus, + hasResolved: hasResolvedNavigationMenus, + } = useEntityRecords( + 'postType', + `wp_navigation`, + PRELOADED_NAVIGATION_MENUS_QUERY + ); + + const canSwitchNavigationMenu = ref + ? navigationMenus?.length > 1 + : navigationMenus?.length > 0; return { - navigationMenus: getEntityRecords( ...SELECT_NAVIGATION_MENUS_ARGS ), - isResolvingNavigationMenus: isResolving( - 'getEntityRecords', - SELECT_NAVIGATION_MENUS_ARGS - ), - hasResolvedNavigationMenus: hasFinishedResolution( - 'getEntityRecords', - SELECT_NAVIGATION_MENUS_ARGS - ), + navigationMenu, + isNavigationMenuResolved, + isNavigationMenuMissing, + navigationMenus, + isResolvingNavigationMenus, + hasResolvedNavigationMenus, + canSwitchNavigationMenu, + canUserCreateNavigationMenu: canCreate, + isResolvingCanUserCreateNavigationMenu: isResolving, + hasResolvedCanUserCreateNavigationMenu: hasResolved, + canUserUpdateNavigationMenu: canUpdate, + hasResolvedCanUserUpdateNavigationMenu: ref ? hasResolved : undefined, + canUserDeleteNavigationMenu: canDelete, + hasResolvedCanUserDeleteNavigationMenu: ref ? hasResolved : undefined, }; } diff --git a/packages/block-library/src/navigation/view-interactivity.js b/packages/block-library/src/navigation/view-interactivity.js new file mode 100644 index 00000000000000..b0d39ef3ca4d57 --- /dev/null +++ b/packages/block-library/src/navigation/view-interactivity.js @@ -0,0 +1,196 @@ +/** + * WordPress dependencies + */ +import { store as wpStore } from '@wordpress/interactivity'; + +const focusableSelectors = [ + 'a[href]', + 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])', + 'select:not([disabled]):not([aria-hidden])', + 'textarea:not([disabled]):not([aria-hidden])', + 'button:not([disabled]):not([aria-hidden])', + '[contenteditable]', + '[tabindex]:not([tabindex^="-"])', +]; + +const openMenu = ( store, menuOpenedOn ) => { + const { context, ref, selectors } = store; + selectors.core.navigation.menuOpenedBy( store )[ menuOpenedOn ] = true; + context.core.navigation.previousFocus = ref; + if ( context.core.navigation.type === 'overlay' ) { + // Add a `has-modal-open` class to the root. + document.documentElement.classList.add( 'has-modal-open' ); + } +}; + +const closeMenu = ( store, menuClosedOn ) => { + const { context, selectors } = store; + selectors.core.navigation.menuOpenedBy( store )[ menuClosedOn ] = false; + // Check if the menu is still open or not. + if ( ! selectors.core.navigation.isMenuOpen( store ) ) { + if ( + context.core.navigation.modal?.contains( + window.document.activeElement + ) + ) { + context.core.navigation.previousFocus.focus(); + } + context.core.navigation.modal = null; + context.core.navigation.previousFocus = null; + if ( context.core.navigation.type === 'overlay' ) { + document.documentElement.classList.remove( 'has-modal-open' ); + } + } +}; + +wpStore( { + effects: { + core: { + navigation: { + initMenu: ( store ) => { + const { context, selectors, ref } = store; + if ( selectors.core.navigation.isMenuOpen( store ) ) { + const focusableElements = + ref.querySelectorAll( focusableSelectors ); + context.core.navigation.modal = ref; + context.core.navigation.firstFocusableElement = + focusableElements[ 0 ]; + context.core.navigation.lastFocusableElement = + focusableElements[ focusableElements.length - 1 ]; + } + }, + focusFirstElement: ( store ) => { + const { selectors, ref } = store; + if ( selectors.core.navigation.isMenuOpen( store ) ) { + ref.querySelector( + '.wp-block-navigation-item > *:first-child' + ).focus(); + } + }, + }, + }, + }, + selectors: { + core: { + navigation: { + roleAttribute: ( store ) => { + const { context, selectors } = store; + return context.core.navigation.type === 'overlay' && + selectors.core.navigation.isMenuOpen( store ) + ? 'dialog' + : ''; + }, + isMenuOpen: ( { context } ) => + // The menu is opened if either `click`, `hover` or `focus` is true. + Object.values( + context.core.navigation[ + context.core.navigation.type === 'overlay' + ? 'overlayOpenedBy' + : 'submenuOpenedBy' + ] + ).filter( Boolean ).length > 0, + menuOpenedBy: ( { context } ) => + context.core.navigation[ + context.core.navigation.type === 'overlay' + ? 'overlayOpenedBy' + : 'submenuOpenedBy' + ], + }, + }, + }, + actions: { + core: { + navigation: { + openMenuOnHover( store ) { + const { navigation } = store.context.core; + if ( + navigation.type === 'submenu' && + // Only open on hover if the overlay is closed. + Object.values( + navigation.overlayOpenedBy || {} + ).filter( Boolean ).length === 0 + ) + openMenu( store, 'hover' ); + }, + closeMenuOnHover( store ) { + closeMenu( store, 'hover' ); + }, + openMenuOnClick( store ) { + openMenu( store, 'click' ); + }, + closeMenuOnClick( store ) { + closeMenu( store, 'click' ); + closeMenu( store, 'focus' ); + }, + openMenuOnFocus( store ) { + openMenu( store, 'focus' ); + }, + toggleMenuOnClick: ( store ) => { + const { selectors } = store; + const menuOpenedBy = + selectors.core.navigation.menuOpenedBy( store ); + if ( menuOpenedBy.click || menuOpenedBy.focus ) { + closeMenu( store, 'click' ); + closeMenu( store, 'focus' ); + } else { + openMenu( store, 'click' ); + } + }, + handleMenuKeydown: ( store ) => { + const { context, selectors, event } = store; + if ( + selectors.core.navigation.menuOpenedBy( store ).click + ) { + // If Escape close the menu. + if ( event?.key === 'Escape' ) { + closeMenu( store, 'click' ); + closeMenu( store, 'focus' ); + return; + } + + // Trap focus if it is an overlay (main menu). + if ( + context.core.navigation.type === 'overlay' && + event.key === 'Tab' + ) { + // If shift + tab it change the direction. + if ( + event.shiftKey && + window.document.activeElement === + context.core.navigation + .firstFocusableElement + ) { + event.preventDefault(); + context.core.navigation.lastFocusableElement.focus(); + } else if ( + ! event.shiftKey && + window.document.activeElement === + context.core.navigation.lastFocusableElement + ) { + event.preventDefault(); + context.core.navigation.firstFocusableElement.focus(); + } + } + } + }, + handleMenuFocusout: ( store ) => { + const { context, event } = store; + // If focus is outside modal, and in the document, close menu + // event.target === The element losing focus + // event.relatedTarget === The element receiving focus (if any) + // When focusout is outsite the document, + // `window.document.activeElement` doesn't change. + if ( + ! context.core.navigation.modal?.contains( + event.relatedTarget + ) && + event.target !== window.document.activeElement + ) { + closeMenu( store, 'click' ); + closeMenu( store, 'focus' ); + } + }, + }, + }, + }, +} ); diff --git a/packages/block-library/src/navigation/view-modal.js b/packages/block-library/src/navigation/view-modal.js new file mode 100644 index 00000000000000..9477d262816d93 --- /dev/null +++ b/packages/block-library/src/navigation/view-modal.js @@ -0,0 +1,78 @@ +/** + * External dependencies + */ +import MicroModal from 'micromodal'; + +// Responsive navigation toggle. +function navigationToggleModal( modal ) { + const dialogContainer = modal.querySelector( + `.wp-block-navigation__responsive-dialog` + ); + + const isHidden = 'true' === modal.getAttribute( 'aria-hidden' ); + + modal.classList.toggle( 'has-modal-open', ! isHidden ); + dialogContainer.toggleAttribute( 'aria-modal', ! isHidden ); + + if ( isHidden ) { + dialogContainer.removeAttribute( 'role' ); + dialogContainer.removeAttribute( 'aria-modal' ); + } else { + dialogContainer.setAttribute( 'role', 'dialog' ); + dialogContainer.setAttribute( 'aria-modal', 'true' ); + } + + // Add a class to indicate the modal is open. + const htmlElement = document.documentElement; + htmlElement.classList.toggle( 'has-modal-open' ); +} + +function isLinkToAnchorOnCurrentPage( node ) { + return ( + node.hash && + node.protocol === window.location.protocol && + node.host === window.location.host && + node.pathname === window.location.pathname && + node.search === window.location.search + ); +} + +window.addEventListener( 'load', () => { + MicroModal.init( { + onShow: navigationToggleModal, + onClose: navigationToggleModal, + openClass: 'is-menu-open', + } ); + + // Close modal automatically on clicking anchor links inside modal. + const navigationLinks = document.querySelectorAll( + '.wp-block-navigation-item__content' + ); + + navigationLinks.forEach( function ( link ) { + // Ignore non-anchor links and anchor links which open on a new tab. + if ( + ! isLinkToAnchorOnCurrentPage( link ) || + link.attributes?.target === '_blank' + ) { + return; + } + + // Find the specific parent modal for this link + // since .close() won't work without an ID if there are + // multiple navigation menus in a post/page. + const modal = link.closest( + '.wp-block-navigation__responsive-container' + ); + const modalId = modal?.getAttribute( 'id' ); + + link.addEventListener( 'click', () => { + // check if modal exists and is open before trying to close it + // otherwise Micromodal will toggle the `has-modal-open` class + // on the html tag which prevents scrolling + if ( modalId && modal.classList.contains( 'has-modal-open' ) ) { + MicroModal.close( modalId ); + } + } ); + } ); +} ); diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js index c9e0a78d6a2a3e..19805a44ae4ae2 100644 --- a/packages/block-library/src/navigation/view.js +++ b/packages/block-library/src/navigation/view.js @@ -1,169 +1,74 @@ -/** - * WordPress dependencies - */ -import { store } from '@wordpress/interactivity'; +// Open on click functionality. +function closeSubmenus( element ) { + element + .querySelectorAll( '[aria-expanded="true"]' ) + .forEach( function ( toggle ) { + toggle.setAttribute( 'aria-expanded', 'false' ); + } ); +} -const focusableSelectors = [ - 'a[href]', - 'area[href]', - 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])', - 'select:not([disabled]):not([aria-hidden])', - 'textarea:not([disabled]):not([aria-hidden])', - 'button:not([disabled]):not([aria-hidden])', - 'iframe', - 'object', - 'embed', - '[contenteditable]', - '[tabindex]:not([tabindex^="-"])', -]; +function toggleSubmenuOnClick( event ) { + const buttonToggle = event.target.closest( '[aria-expanded]' ); + const isSubmenuOpen = buttonToggle.getAttribute( 'aria-expanded' ); -const openMenu = ( { context, ref }, menuOpenedOn ) => { - context.core.navigation.isMenuOpen[ menuOpenedOn ] = true; - context.core.navigation.previousFocus = ref; - if ( context.core.navigation.overlay ) { - // Add a `has-modal-open` class to the root. - document.documentElement.classList.add( 'has-modal-open' ); + if ( isSubmenuOpen === 'true' ) { + closeSubmenus( buttonToggle.closest( '.wp-block-navigation-item' ) ); + } else { + // Close all sibling submenus. + const parentElement = buttonToggle.closest( + '.wp-block-navigation-item' + ); + const navigationParent = buttonToggle.closest( + '.wp-block-navigation__submenu-container, .wp-block-navigation__container, .wp-block-page-list' + ); + navigationParent + .querySelectorAll( '.wp-block-navigation-item' ) + .forEach( function ( child ) { + if ( child !== parentElement ) { + closeSubmenus( child ); + } + } ); + // Open submenu. + buttonToggle.setAttribute( 'aria-expanded', 'true' ); } -}; +} -const closeMenu = ( { context, selectors }, menuClosedOn ) => { - context.core.navigation.isMenuOpen[ menuClosedOn ] = false; - // Check if the menu is still open or not. - if ( ! selectors.core.navigation.isMenuOpen( { context } ) ) { - if ( - context.core.navigation.modal?.contains( - window.document.activeElement - ) - ) { - context.core.navigation.previousFocus.focus(); - } - context.core.navigation.modal = null; - context.core.navigation.previousFocus = null; - if ( context.core.navigation.overlay ) { - document.documentElement.classList.remove( 'has-modal-open' ); - } - } -}; +// Necessary for some themes such as TT1 Blocks, where +// scripts could be loaded before the body. +window.addEventListener( 'load', () => { + const submenuButtons = document.querySelectorAll( + '.wp-block-navigation-submenu__toggle' + ); -store( { - effects: { - core: { - navigation: { - initMenu: ( { context, selectors, ref } ) => { - if ( selectors.core.navigation.isMenuOpen( { context } ) ) { - const focusableElements = - ref.querySelectorAll( focusableSelectors ); - context.core.navigation.modal = ref; - context.core.navigation.firstFocusableElement = - focusableElements[ 0 ]; - context.core.navigation.lastFocusableElement = - focusableElements[ focusableElements.length - 1 ]; - } - }, - focusFirstElement: ( { context, selectors, ref } ) => { - if ( selectors.core.navigation.isMenuOpen( { context } ) ) { - ref.querySelector( - '.wp-block-navigation-item > *:first-child' - ).focus(); - } - }, - }, - }, - }, - selectors: { - core: { - navigation: { - roleAttribute: ( { context, selectors } ) => - context.core.navigation.overlay && - selectors.core.navigation.isMenuOpen( { context } ) - ? 'dialog' - : '', - isMenuOpen: ( { context } ) => - // The menu is opened if either `click` or `hover` is true. - Object.values( context.core.navigation.isMenuOpen ).filter( - Boolean - ).length > 0, - }, - }, - }, - actions: { - core: { - navigation: { - openMenuOnHover( args ) { - openMenu( args, 'hover' ); - }, - closeMenuOnHover( args ) { - closeMenu( args, 'hover' ); - }, - openMenuOnClick( args ) { - openMenu( args, 'click' ); - }, - closeMenuOnClick( args ) { - closeMenu( args, 'click' ); - }, - toggleMenuOnClick: ( args ) => { - const { context } = args; - if ( context.core.navigation.isMenuOpen.click ) { - closeMenu( args, 'click' ); - } else { - openMenu( args, 'click' ); - } - }, - handleMenuKeydown: ( args ) => { - const { context, event } = args; - if ( context.core.navigation.isMenuOpen.click ) { - // If Escape close the menu - if ( - event?.key === 'Escape' || - event?.keyCode === 27 - ) { - closeMenu( args, 'click' ); - return; - } + submenuButtons.forEach( function ( button ) { + button.addEventListener( 'click', toggleSubmenuOnClick ); + } ); - // Trap focus if it is an overlay (main menu) - if ( - context.core.navigation.overlay && - ( event.key === 'Tab' || event.keyCode === 9 ) - ) { - // If shift + tab it change the direction - if ( - event.shiftKey && - window.document.activeElement === - context.core.navigation - .firstFocusableElement - ) { - event.preventDefault(); - context.core.navigation.lastFocusableElement.focus(); - } else if ( - ! event.shiftKey && - window.document.activeElement === - context.core.navigation.lastFocusableElement - ) { - event.preventDefault(); - context.core.navigation.firstFocusableElement.focus(); - } - } - } - }, - handleMenuFocusout: ( args ) => { - const { context, event } = args; - // If focus is outside modal, and in the document, close menu - // event.target === The element losing focus - // event.relatedTarget === The element receiving focus (if any) - // When focusout is outsite the document, - // `window.document.activeElement` doesn't change - if ( - context.core.navigation.isMenuOpen.click && - ! context.core.navigation.modal?.contains( - event.relatedTarget - ) && - event.target !== window.document.activeElement - ) { - closeMenu( args, 'click' ); - } - }, - }, - }, - }, + // Close on click outside. + document.addEventListener( 'click', function ( event ) { + const navigationBlocks = document.querySelectorAll( + '.wp-block-navigation' + ); + navigationBlocks.forEach( function ( block ) { + if ( ! block.contains( event.target ) ) { + closeSubmenus( block ); + } + } ); + } ); + // Close on focus outside or escape key. + document.addEventListener( 'keyup', function ( event ) { + const submenuBlocks = document.querySelectorAll( + '.wp-block-navigation-item.has-child' + ); + submenuBlocks.forEach( function ( block ) { + if ( ! block.contains( event.target ) ) { + closeSubmenus( block ); + } else if ( event.key === 'Escape' ) { + const toggle = block.querySelector( '[aria-expanded="true"]' ); + closeSubmenus( block ); + // Focus the submenu trigger so focus does not get trapped in the closed submenu. + toggle?.focus(); + } + } ); + } ); } ); diff --git a/packages/block-library/src/page-list/convert-to-links-modal.js b/packages/block-library/src/page-list/convert-to-links-modal.js index b212fccb774177..cd4049fecff588 100644 --- a/packages/block-library/src/page-list/convert-to-links-modal.js +++ b/packages/block-library/src/page-list/convert-to-links-modal.js @@ -5,14 +5,14 @@ import { Button, Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; export const convertDescription = __( - 'This menu is automatically kept in sync with pages on your site. You can manage the menu yourself by clicking "Edit" below.' + 'This page list is synced with the published pages on your site. Detach the page list to add, delete, or reorder pages yourself.' ); export function ConvertToLinksModal( { onClick, onClose, disabled } ) { return ( - { __( 'Edit' ) } + { __( 'Detach' ) }
    5. diff --git a/packages/block-library/src/paragraph/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/paragraph/test/__snapshots__/edit.native.js.snap index c1bcb2e853c8cd..adc6ab4210efa5 100644 --- a/packages/block-library/src/paragraph/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/paragraph/test/__snapshots__/edit.native.js.snap @@ -32,6 +32,7 @@ exports[`Paragraph block should render without crashing and match snapshot 1`] = onSelectionChange={[Function]} placeholder="Start writing…" placeholderTextColor="gray" + selectionColor="black" triggerKeyCodes={[]} value="" /> diff --git a/packages/block-library/src/paragraph/test/edit.native.js b/packages/block-library/src/paragraph/test/edit.native.js index 2f820be9857436..e1f73119d5393e 100644 --- a/packages/block-library/src/paragraph/test/edit.native.js +++ b/packages/block-library/src/paragraph/test/edit.native.js @@ -651,4 +651,30 @@ describe( 'Paragraph block', () => { ); expect( contrastCheckElement ).toBeDefined(); } ); + + it( 'should highlight text with selection', async () => { + // Arrange + const screen = await initializeEditor( { withGlobalStyles: true } ); + await addBlock( screen, 'Paragraph' ); + + // Act + const paragraphBlock = getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + const paragraphTextInput = + within( paragraphBlock ).getByPlaceholderText( 'Start writing…' ); + typeInRichText( + paragraphTextInput, + 'A quick brown fox jumps over the lazy dog.', + { finalSelectionStart: 2, finalSelectionEnd: 7 } + ); + fireEvent.press( screen.getByLabelText( 'Text color' ) ); + fireEvent.press( await screen.findByLabelText( 'Tertiary' ) ); + + // Assert + expect( getEditorHtml() ).toMatchInlineSnapshot( ` + " +

      A quick brown fox jumps over the lazy dog.

      + " + ` ); + } ); } ); diff --git a/packages/block-library/src/post-template/index.php b/packages/block-library/src/post-template/index.php index 3c023c80ed263d..b1499d845f39a6 100644 --- a/packages/block-library/src/post-template/index.php +++ b/packages/block-library/src/post-template/index.php @@ -97,11 +97,13 @@ function render_block_core_post_template( $attributes, $content, $block ) { $context['postId'] = $post_id; return $context; }; - add_filter( 'render_block_context', $filter_block_context ); + + // Use an early priority to so that other 'render_block_context' filters have access to the values. + add_filter( 'render_block_context', $filter_block_context, 1 ); // Render the inner blocks of the Post Template block with `dynamic` set to `false` to prevent calling // `render_callback` and ensure that no wrapper markup is included. $block_content = ( new WP_Block( $block_instance ) )->render( array( 'dynamic' => false ) ); - remove_filter( 'render_block_context', $filter_block_context ); + remove_filter( 'render_block_context', $filter_block_context, 1 ); // Wrap the render inner blocks in a `li` element with the appropriate post classes. $post_classes = implode( ' ', get_post_class( 'wp-block-post' ) ); diff --git a/packages/block-library/src/post-title/index.php b/packages/block-library/src/post-title/index.php index e1d4b255c57733..1769b199cebf17 100644 --- a/packages/block-library/src/post-title/index.php +++ b/packages/block-library/src/post-title/index.php @@ -19,8 +19,11 @@ function render_block_core_post_title( $attributes, $content, $block ) { return ''; } - $post = get_post( $block->context['postId'] ); - $title = get_the_title( $post ); + /** + * The `$post` argument is intentionally omitted so that changes are reflected when previewing a post. + * See: https://github.com/WordPress/gutenberg/pull/37622#issuecomment-1000932816. + */ + $title = get_the_title(); if ( ! $title ) { return ''; @@ -33,7 +36,7 @@ function render_block_core_post_title( $attributes, $content, $block ) { if ( isset( $attributes['isLink'] ) && $attributes['isLink'] ) { $rel = ! empty( $attributes['rel'] ) ? 'rel="' . esc_attr( $attributes['rel'] ) . '"' : ''; - $title = sprintf( '%4$s', get_the_permalink( $post ), esc_attr( $attributes['linkTarget'] ), $rel, $title ); + $title = sprintf( '%4$s', get_the_permalink( $block->context['postId'] ), esc_attr( $attributes['linkTarget'] ), $rel, $title ); } $classes = array(); 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 index 7c0a7aa1dcf48c..db1c80c514206b 100644 --- a/packages/block-library/src/preformatted/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/preformatted/test/__snapshots__/edit.native.js.snap @@ -39,6 +39,7 @@ exports[`Preformatted should match snapshot when content is empty 1`] = ` onSelectionChange={[Function]} placeholder="Write preformatted text…" placeholderTextColor="gray" + selectionColor="black" triggerKeyCodes={[]} value="" /> @@ -85,6 +86,7 @@ exports[`Preformatted should match snapshot when content is not empty 1`] = ` onSelectionChange={[Function]} placeholder="Write preformatted text…" placeholderTextColor="gray" + selectionColor="black" triggerKeyCodes={[]} value="
      Hello World!
      " /> diff --git a/packages/block-library/src/query-pagination/edit.js b/packages/block-library/src/query-pagination/edit.js index 8ab1f63377949b..7598eba5c1cacf 100644 --- a/packages/block-library/src/query-pagination/edit.js +++ b/packages/block-library/src/query-pagination/edit.js @@ -34,20 +34,23 @@ export default function QueryPaginationEdit( { setAttributes, clientId, } ) { - const hasNextPreviousBlocks = useSelect( ( select ) => { - const { getBlocks } = select( blockEditorStore ); - const innerBlocks = getBlocks( clientId ); - /** - * Show the `paginationArrow` and `showLabel` controls only if a - * `QueryPaginationNext/Previous` block exists. - */ - return innerBlocks?.find( ( innerBlock ) => { - return [ - 'core/query-pagination-next', - 'core/query-pagination-previous', - ].includes( innerBlock.name ); - } ); - }, [] ); + const hasNextPreviousBlocks = useSelect( + ( select ) => { + const { getBlocks } = select( blockEditorStore ); + const innerBlocks = getBlocks( clientId ); + /** + * Show the `paginationArrow` and `showLabel` controls only if a + * `QueryPaginationNext/Previous` block exists. + */ + return innerBlocks?.find( ( innerBlock ) => { + return [ + 'core/query-pagination-next', + 'core/query-pagination-previous', + ].includes( innerBlock.name ); + } ); + }, + [ clientId ] + ); const blockProps = useBlockProps(); const innerBlocksProps = useInnerBlocksProps( blockProps, { template: TEMPLATE, diff --git a/packages/block-library/src/query-title/edit.js b/packages/block-library/src/query-title/edit.js index e5fa6f6c396774..b74e03e7583b2d 100644 --- a/packages/block-library/src/query-title/edit.js +++ b/packages/block-library/src/query-title/edit.js @@ -13,9 +13,11 @@ import { useBlockProps, Warning, HeadingLevelDropdown, + store as blockEditorStore, } from '@wordpress/block-editor'; import { ToggleControl, PanelBody } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; const SUPPORTED_TYPES = [ 'archive', 'search' ]; @@ -23,6 +25,18 @@ export default function QueryTitleEdit( { attributes: { type, level, textAlign, showPrefix, showSearchTerm }, setAttributes, } ) { + const { archiveTypeTitle, archiveNameLabel } = useSelect( ( select ) => { + const { getSettings } = select( blockEditorStore ); + const { + __experimentalArchiveTitleNameLabel, + __experimentalArchiveTitleTypeLabel, + } = getSettings(); + return { + archiveTypeTitle: __experimentalArchiveTitleTypeLabel, + archiveNameLabel: __experimentalArchiveTitleNameLabel, + }; + } ); + const TagName = `h${ level }`; const blockProps = useBlockProps( { className: classnames( 'wp-block-query-title__placeholder', { @@ -40,6 +54,38 @@ export default function QueryTitleEdit( { let titleElement; if ( type === 'archive' ) { + let title; + if ( archiveTypeTitle ) { + if ( showPrefix ) { + if ( archiveNameLabel ) { + title = sprintf( + /* translators: 1: Archive type title e.g: "Category", 2: Label of the archive e.g: "Shoes" */ + __( '%1$s: %2$s' ), + archiveTypeTitle, + archiveNameLabel + ); + } else { + title = sprintf( + /* translators: %s: Archive type title e.g: "Category", "Tag"... */ + __( '%s: Name' ), + archiveTypeTitle + ); + } + } else if ( archiveNameLabel ) { + title = archiveNameLabel; + } else { + title = sprintf( + /* translators: %s: Archive type title e.g: "Category", "Tag"... */ + __( '%s name' ), + archiveTypeTitle + ); + } + } else { + title = showPrefix + ? __( 'Archive type: Name' ) + : __( 'Archive title' ); + } + titleElement = ( <> @@ -54,11 +100,7 @@ export default function QueryTitleEdit( { /> - - { showPrefix - ? __( 'Archive type: Name' ) - : __( 'Archive title' ) } - + { title } ); } diff --git a/packages/block-library/src/quote/test/__snapshots__/transforms.native.js.snap b/packages/block-library/src/quote/test/__snapshots__/transforms.native.js.snap index 5b5df918f2beeb..65d87d5b0d7bd4 100644 --- a/packages/block-library/src/quote/test/__snapshots__/transforms.native.js.snap +++ b/packages/block-library/src/quote/test/__snapshots__/transforms.native.js.snap @@ -22,6 +22,16 @@ exports[`Quote block transforms to Group block 1`] = ` " `; +exports[`Quote block transforms to Paragraph block 1`] = ` +" +

      "This will make running your own blog a viable alternative again."

      + + + +

      Adrian Zumbrunnen

      +" +`; + exports[`Quote block transforms to Pullquote block 1`] = ` "

      "This will make running your own blog a viable alternative again."

      Adrian Zumbrunnen
      diff --git a/packages/block-library/src/quote/test/transforms.native.js b/packages/block-library/src/quote/test/transforms.native.js index 46c4eb2b6f9727..25030e0a018d41 100644 --- a/packages/block-library/src/quote/test/transforms.native.js +++ b/packages/block-library/src/quote/test/transforms.native.js @@ -21,7 +21,11 @@ const initialHtml = ` `; const transformsWithInnerBlocks = [ 'Columns', 'Group' ]; -const blockTransforms = [ 'Pullquote', ...transformsWithInnerBlocks ]; +const blockTransforms = [ + 'Pullquote', + 'Paragraph', + ...transformsWithInnerBlocks, +]; setupCoreBlocks(); diff --git a/packages/block-library/src/quote/transforms.js b/packages/block-library/src/quote/transforms.js index d4cd77177bf030..4e153a6399029f 100644 --- a/packages/block-library/src/quote/transforms.js +++ b/packages/block-library/src/quote/transforms.js @@ -109,6 +109,19 @@ const transforms = { } ); }, }, + { + type: 'block', + blocks: [ 'core/paragraph' ], + transform: ( { citation }, innerBlocks ) => + citation + ? [ + ...innerBlocks, + createBlock( 'core/paragraph', { + content: citation, + } ), + ] + : innerBlocks, + }, { type: 'block', blocks: [ 'core/group' ], diff --git a/packages/block-library/src/rss/edit.js b/packages/block-library/src/rss/edit.js index 2fd94cdd2a021a..d24de5c291d511 100644 --- a/packages/block-library/src/rss/edit.js +++ b/packages/block-library/src/rss/edit.js @@ -116,6 +116,7 @@ export default function RSSEdit( { attributes, setAttributes } ) { @@ -146,6 +147,7 @@ export default function RSSEdit( { attributes, setAttributes } ) { { displayExcerpt && ( @@ -159,6 +161,7 @@ export default function RSSEdit( { attributes, setAttributes } ) { { blockLayout === 'grid' && ( diff --git a/packages/block-library/src/search/block.json b/packages/block-library/src/search/block.json index 1a23590699af4e..b2873bfa8e5729 100644 --- a/packages/block-library/src/search/block.json +++ b/packages/block-library/src/search/block.json @@ -90,6 +90,7 @@ }, "html": false }, + "viewScript": "file:./view.min.js", "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 7cfa293076b3d1..80238ab1741f7e 100644 --- a/packages/block-library/src/search/edit.js +++ b/packages/block-library/src/search/edit.js @@ -97,8 +97,8 @@ export default function SearchEdit( { ); const { __unstableMarkNextChangeAsNotPersistent } = useDispatch( blockEditorStore ); - useEffect( () => { - if ( ! insertedInNavigationBlock ) return; + + if ( insertedInNavigationBlock ) { // This side-effect should not create an undo level. __unstableMarkNextChangeAsNotPersistent(); setAttributes( { @@ -106,7 +106,8 @@ export default function SearchEdit( { buttonUseIcon: true, buttonPosition: 'button-inside', } ); - }, [ insertedInNavigationBlock ] ); + } + const borderRadius = style?.border?.radius; const borderProps = useBorderProps( attributes ); diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php index ce76587dbbb441..700a299a92457a 100644 --- a/packages/block-library/src/search/index.php +++ b/packages/block-library/src/search/index.php @@ -8,11 +8,13 @@ /** * Dynamically renders the `core/search` block. * - * @param array $attributes The block attributes. + * @param array $attributes The block attributes. + * @param string $content The saved content. + * @param WP_Block $block The parsed block. * * @return string The search block markup. */ -function render_block_core_search( $attributes ) { +function render_block_core_search( $attributes, $content, $block ) { // Older versions of the Search block defaulted the label and buttonText // attributes to `__( 'Search' )` meaning that many posts contain ``. Support these by defaulting an undefined label and @@ -70,10 +72,26 @@ function render_block_core_search( $attributes ) { $input->set_attribute( 'id', $input_id ); $input->set_attribute( 'value', get_search_query() ); $input->set_attribute( 'placeholder', $attributes['placeholder'] ); - if ( 'button-only' === $button_position && 'expand-searchfield' === $button_behavior ) { + + $is_expandable_searchfield = 'button-only' === $button_position && 'expand-searchfield' === $button_behavior; + if ( $is_expandable_searchfield ) { $input->set_attribute( 'aria-hidden', 'true' ); $input->set_attribute( 'tabindex', '-1' ); - wp_enqueue_script( 'wp-block--search-view', plugins_url( 'search/view.min.js', __FILE__ ) ); + } + + // If the script already exists, there is no point in removing it from viewScript. + $view_js_file = 'wp-block-search-view'; + if ( ! wp_script_is( $view_js_file ) ) { + $script_handles = $block->block_type->view_script_handles; + + // If the script is not needed, and it is still in the `view_script_handles`, remove it. + if ( ! $is_expandable_searchfield && in_array( $view_js_file, $script_handles, true ) ) { + $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file ) ); + } + // If the script is needed, but it was previously removed, add it again. + if ( $is_expandable_searchfield && ! in_array( $view_js_file, $script_handles, true ) ) { + $block->block_type->view_script_handles = array_merge( $script_handles, array( $view_js_file ) ); + } } } diff --git a/packages/block-library/src/search/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/search/test/__snapshots__/edit.native.js.snap index 43afad0f6c2612..e40797aea57757 100644 --- a/packages/block-library/src/search/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/search/test/__snapshots__/edit.native.js.snap @@ -61,6 +61,7 @@ exports[`Search Block renders block with button inside option 1`] = ` onStartShouldSetResponder={[Function]} placeholder="Add label…" placeholderTextColor="gray" + selectionColor="black" style={ { "backgroundColor": undefined, @@ -186,6 +187,7 @@ exports[`Search Block renders block with button inside option 1`] = ` onStartShouldSetResponder={[Function]} placeholder="" placeholderTextColor="gray" + selectionColor="black" style={ { "backgroundColor": undefined, @@ -273,6 +275,7 @@ exports[`Search Block renders block with icon button option matches snapshot 1`] onStartShouldSetResponder={[Function]} placeholder="Add label…" placeholderTextColor="gray" + selectionColor="black" style={ { "backgroundColor": undefined, @@ -468,6 +471,7 @@ exports[`Search Block renders block with label hidden matches snapshot 1`] = ` onStartShouldSetResponder={[Function]} placeholder="" placeholderTextColor="gray" + selectionColor="black" style={ { "backgroundColor": undefined, @@ -555,6 +559,7 @@ exports[`Search Block renders with default configuration matches snapshot 1`] = onStartShouldSetResponder={[Function]} placeholder="Add label…" placeholderTextColor="gray" + selectionColor="black" style={ { "backgroundColor": undefined, @@ -680,6 +685,7 @@ exports[`Search Block renders with default configuration matches snapshot 1`] = onStartShouldSetResponder={[Function]} placeholder="" placeholderTextColor="gray" + selectionColor="black" style={ { "backgroundColor": undefined, @@ -767,6 +773,7 @@ exports[`Search Block renders with no-button option matches snapshot 1`] = ` onStartShouldSetResponder={[Function]} placeholder="Add label…" placeholderTextColor="gray" + selectionColor="black" style={ { "backgroundColor": undefined, diff --git a/packages/block-library/src/site-logo/edit.js b/packages/block-library/src/site-logo/edit.js index 8fe86d6d4a6dec..cddfb5dfdc4fc4 100644 --- a/packages/block-library/src/site-logo/edit.js +++ b/packages/block-library/src/site-logo/edit.js @@ -293,6 +293,7 @@ const SiteLogo = ( { setAttributes( { width: newWidth } ) diff --git a/packages/block-library/src/tag-cloud/edit.js b/packages/block-library/src/tag-cloud/edit.js index 714de24a38a149..4dbec86fff520c 100644 --- a/packages/block-library/src/tag-cloud/edit.js +++ b/packages/block-library/src/tag-cloud/edit.js @@ -122,6 +122,7 @@ function TagCloudEdit( { attributes, setAttributes, taxonomies } ) { /> diff --git a/packages/block-library/src/template-part/edit/index.js b/packages/block-library/src/template-part/edit/index.js index b7ae4f6043afb1..a4d2e5f2a017e2 100644 --- a/packages/block-library/src/template-part/edit/index.js +++ b/packages/block-library/src/template-part/edit/index.js @@ -77,7 +77,7 @@ export default function TemplatePartEdit( { area: _area, }; }, - [ templatePartId, clientId ] + [ templatePartId, attributes.area, clientId ] ); const { templateParts } = useAlternativeTemplateParts( area, diff --git a/packages/block-library/src/template-part/index.php b/packages/block-library/src/template-part/index.php index 84e3a985ec4410..1066aa01419159 100644 --- a/packages/block-library/src/template-part/index.php +++ b/packages/block-library/src/template-part/index.php @@ -18,12 +18,11 @@ function render_block_core_template_part( $attributes ) { $template_part_id = null; $content = null; $area = WP_TEMPLATE_PART_AREA_UNCATEGORIZED; - $stylesheet = get_stylesheet(); if ( isset( $attributes['slug'] ) && isset( $attributes['theme'] ) && - $stylesheet === $attributes['theme'] + get_stylesheet() === $attributes['theme'] ) { $template_part_id = $attributes['theme'] . '//' . $attributes['slug']; $template_part_query = new WP_Query( @@ -64,22 +63,17 @@ function render_block_core_template_part( $attributes ) { */ do_action( 'render_block_core_template_part_post', $template_part_id, $attributes, $template_part_post, $content ); } else { + $template_part_file_path = ''; // Else, if the template part was provided by the active theme, // render the corresponding file content. if ( 0 === validate_file( $attributes['slug'] ) ) { - $themes = array( $stylesheet ); - $template = get_template(); - if ( $stylesheet !== $template ) { - $themes[] = $template; - } - - foreach ( $themes as $theme ) { - $theme_folders = get_block_theme_folders( $theme ); - $template_part_file_path = get_theme_file_path( '/' . $theme_folders['wp_template_part'] . '/' . $attributes['slug'] . '.html' ); - if ( file_exists( $template_part_file_path ) ) { - $content = (string) file_get_contents( $template_part_file_path ); - $content = '' !== $content ? _inject_theme_attribute_in_block_template_content( $content ) : ''; - break; + $block_template_file = _get_block_template_file( 'wp_template_part', $attributes['slug'] ); + if ( $block_template_file ) { + $template_part_file_path = $block_template_file['path']; + $content = (string) file_get_contents( $template_part_file_path ); + $content = '' !== $content ? _inject_theme_attribute_in_block_template_content( $content ) : ''; + if ( isset( $block_template_file['area'] ) ) { + $area = $block_template_file['area']; } } } diff --git a/packages/block-library/src/text-columns/edit.js b/packages/block-library/src/text-columns/edit.js index 7e7946927b1d29..761d047e90b1c9 100644 --- a/packages/block-library/src/text-columns/edit.js +++ b/packages/block-library/src/text-columns/edit.js @@ -35,6 +35,7 @@ export default function TextColumnsEdit( { attributes, setAttributes } ) { diff --git a/packages/block-serialization-default-parser/CHANGELOG.md b/packages/block-serialization-default-parser/CHANGELOG.md index 6a1e83fff5ab39..d3ca0243114f9c 100644 --- a/packages/block-serialization-default-parser/CHANGELOG.md +++ b/packages/block-serialization-default-parser/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.37.0 (2023-07-05) + ## 4.36.0 (2023-06-23) ## 4.35.0 (2023-06-07) diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index b170b16f02770b..a0b8414ded1e7f 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-default-parser", - "version": "4.36.0", + "version": "4.37.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-serialization-spec-parser/CHANGELOG.md b/packages/block-serialization-spec-parser/CHANGELOG.md index 944bb874da02b3..8f3d032be39c62 100644 --- a/packages/block-serialization-spec-parser/CHANGELOG.md +++ b/packages/block-serialization-spec-parser/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.37.0 (2023-07-05) + ## 4.36.0 (2023-06-23) ## 4.35.0 (2023-06-07) diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index 8c8653198d3484..db2592278c16c7 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-spec-parser", - "version": "4.36.0", + "version": "4.37.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index f26e897aebbd07..249b254a92fa40 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 12.14.0 (2023-07-05) + ## 12.13.0 (2023-06-23) ## 12.12.0 (2023-06-07) diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 582774a9d6a1c0..89ed8b687f447b 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blocks", - "version": "12.13.0", + "version": "12.14.0", "description": "Block API for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/browserslist-config/CHANGELOG.md b/packages/browserslist-config/CHANGELOG.md index cf43f3751f74ba..ce9ff2903239b1 100644 --- a/packages/browserslist-config/CHANGELOG.md +++ b/packages/browserslist-config/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.20.0 (2023-07-05) + ## 5.19.0 (2023-06-23) ## 5.18.0 (2023-06-07) diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index d90ecf77f0ce0a..be81cad67529ff 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/browserslist-config", - "version": "5.19.0", + "version": "5.20.0", "description": "WordPress Browserslist shared configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/commands/CHANGELOG.md b/packages/commands/CHANGELOG.md index e76f27c9d787ba..1645fa29f2b44e 100644 --- a/packages/commands/CHANGELOG.md +++ b/packages/commands/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 0.8.0 (2023-07-05) + ## 0.7.0 (2023-06-23) ## 0.6.0 (2023-06-07) diff --git a/packages/commands/package.json b/packages/commands/package.json index 92d7cd61bd16c4..8b08f115820479 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/commands", - "version": "0.7.0", + "version": "0.8.0", "description": "Handles the commands menu.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index c690c15bce3e95..f4661358d27a97 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -4,6 +4,20 @@ ### Enhancements +- `TextControl`: Add `id` prop to allow for custom IDs in `TextControl`s ([#52028](https://github.com/WordPress/gutenberg/pull/52028)). +- `Navigator`: Add `replace` option to `navigator.goTo()` and `navigator.goToParent()` ([#52456](https://github.com/WordPress/gutenberg/pull/52456)). + +### Bug Fix + +- `Popover`: Pin `react-dropdown-menu` version to avoid breaking changes in dependency updates. ([#52356](https://github.com/WordPress/gutenberg/pull/52356)). +- `Item`: Unify focus style and add default font styles. ([#52495](https://github.com/WordPress/gutenberg/pull/52495)). +- `Toolbar`: Fix toolbar items not being tabbable on the first render. ([#52613](https://github.com/WordPress/gutenberg/pull/52613)) +- `FormTokenField`: Fix token overflow when moving cursor left or right. ([#52662](https://github.com/WordPress/gutenberg/pull/52662)) + +## 25.3.0 (2023-07-05) + +### Enhancements + - `SelectControl`: Added option to set hidden options. ([#51545](https://github.com/WordPress/gutenberg/pull/51545)) - `RangeControl`: Add `__next40pxDefaultSize` prop to opt into the new 40px default size ([#49105](https://github.com/WordPress/gutenberg/pull/49105)). - `Button`: Introduce `size` prop with `default`, `compact`, and `small` variants ([#51842](https://github.com/WordPress/gutenberg/pull/51842)). @@ -17,6 +31,7 @@ - `Modal`: Add small top padding to the content so that avoid cutting off the visible outline when hovering items ([#51829](https://github.com/WordPress/gutenberg/pull/51829)). - `DropdownMenu`: fix icon style when dashicon is used ([#43574](https://github.com/WordPress/gutenberg/pull/43574)). - `UnitControl`: Fix crash when certain units are used ([#52211](https://github.com/WordPress/gutenberg/pull/52211)). +- `Guide`: Place focus on the guide's container instead of its first tabbable ([#52300](https://github.com/WordPress/gutenberg/pull/52300)). ## 25.2.0 (2023-06-23) @@ -25,6 +40,7 @@ - `UnitControl`: Revamp support for changing unit by typing ([#39303](https://github.com/WordPress/gutenberg/pull/39303)). - `Modal`: Update corner radius to be between buttons and the site view frame, in a 2-4-8 system. ([#51254](https://github.com/WordPress/gutenberg/pull/51254)). - `ItemGroup`: Update button focus state styles to be inline with other button focus states in the editor. ([#51576](https://github.com/WordPress/gutenberg/pull/51576)). +- `ItemGroup`: Update button focus state styles to target `:focus-visible` rather than `:focus`. ([#51787](https://github.com/WordPress/gutenberg/pull/51787)). ### Bug Fix diff --git a/packages/components/package.json b/packages/components/package.json index 3b2ffe6355e4f5..455d3daa3c089d 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "25.2.0", + "version": "25.3.0", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,7 +30,7 @@ ], "types": "build-types", "dependencies": { - "@ariakit/react": "^0.2.10", + "@ariakit/react": "^0.2.12", "@babel/runtime": "^7.16.0", "@emotion/cache": "^11.7.1", "@emotion/css": "^11.7.1", @@ -39,7 +39,7 @@ "@emotion/styled": "^11.6.0", "@emotion/utils": "^1.0.0", "@floating-ui/react-dom": "1.0.0", - "@radix-ui/react-dropdown-menu": "^2.0.4", + "@radix-ui/react-dropdown-menu": "2.0.4", "@use-gesture/react": "^10.2.24", "@wordpress/a11y": "file:../a11y", "@wordpress/compose": "file:../compose", diff --git a/packages/components/src/button/index.native.js b/packages/components/src/button/index.native.js index 02820e1731f71c..95ca9d36b3791b 100644 --- a/packages/components/src/button/index.native.js +++ b/packages/components/src/button/index.native.js @@ -24,6 +24,7 @@ import { */ import Tooltip from '../tooltip'; import Icon from '../icon'; +import style from './style.scss'; const isAndroid = Platform.OS === 'android'; const marginBottom = isAndroid ? -0.5 : 0; @@ -51,8 +52,6 @@ const styles = StyleSheet.create( { justifyContent: 'center', alignItems: 'center', borderRadius: 6, - borderColor: '#2e4453', - backgroundColor: '#2e4453', }, subscriptInactive: { color: '#7b9ab1', // $toolbar-button. @@ -95,6 +94,7 @@ export function Button( props ) { tooltipPosition, isActiveStyle, customContainerStyles, + hitSlop, } = props; const preferredColorScheme = usePreferredColorScheme(); @@ -105,10 +105,16 @@ export function Button( props ) { customContainerStyles && { ...customContainerStyles }, ]; + const buttonActiveColorStyles = usePreferredColorSchemeStyle( + style[ 'components-button-light--active' ], + style[ 'components-button-dark--active' ] + ); + const buttonViewStyle = { opacity: isDisabled ? 0.3 : 1, ...( fixedRatio && styles.fixedRatio ), ...( isPressed ? styles.buttonActive : styles.buttonInactive ), + ...( isPressed ? buttonActiveColorStyles : {} ), ...( isPressed && isActiveStyle?.borderRadius && { borderRadius: isActiveStyle.borderRadius, @@ -158,7 +164,6 @@ export function Button( props ) { const newIcon = icon ? cloneElement( , { - colorScheme: preferredColorScheme, isPressed, } ) : null; @@ -184,6 +189,7 @@ export function Button( props ) { style={ containerStyle } disabled={ isDisabled } testID={ testID } + hitSlop={ hitSlop } > ( null ); + const ref = useRef< HTMLDivElement >( null ); const [ currentPage, setCurrentPage ] = useState( 0 ); + useEffect( () => { + // Place focus at the top of the guide on mount and when the page changes. + const frame = ref.current?.querySelector( '.components-guide' ); + if ( frame instanceof HTMLElement ) { + frame.focus(); + } + }, [ currentPage ] ); + useEffect( () => { if ( Children.count( children ) ) { deprecated( 'Passing children to ', { @@ -71,16 +78,6 @@ function Guide( { } }, [ children ] ); - useEffect( () => { - // Each time we change the current page, start from the first element of the page. - // This also solves any focus loss that can happen. - if ( guideContainer.current ) { - ( - focus.tabbable.find( guideContainer.current ) as HTMLElement[] - )[ 0 ]?.focus(); - } - }, [ currentPage ] ); - if ( Children.count( children ) ) { pages = Children.map( children, ( child ) => ( { @@ -124,7 +121,7 @@ function Guide( { event.preventDefault(); } } } - ref={ guideContainer } + ref={ ref } >
      diff --git a/packages/components/src/item-group/item/hook.ts b/packages/components/src/item-group/item/hook.ts index bd3be96c618b89..ff97e3adae1603 100644 --- a/packages/components/src/item-group/item/hook.ts +++ b/packages/components/src/item-group/item/hook.ts @@ -42,7 +42,8 @@ export function useItem( props: WordPressComponentProps< ItemProps, 'div' > ) { const classes = useMemo( () => cx( - as === 'button' && styles.unstyledButton, + ( as === 'button' || as === 'a' ) && + styles.unstyledButton( as ), styles.itemSizes[ size ] || styles.itemSizes.medium, styles.item, spacedAround && styles.spacedAround, diff --git a/packages/components/src/item-group/stories/index.tsx b/packages/components/src/item-group/stories/index.tsx index 3fa35c67cfb533..e520ff10c160f5 100644 --- a/packages/components/src/item-group/stories/index.tsx +++ b/packages/components/src/item-group/stories/index.tsx @@ -39,20 +39,25 @@ Default.args = { children: ( [ { - children: 'First item', + children: 'First button item', // eslint-disable-next-line no-alert onClick: () => alert( 'First item clicked' ), }, { - children: 'Second item', + children: 'Second button item', // eslint-disable-next-line no-alert onClick: () => alert( 'Second item clicked' ), }, { - children: 'Third item', + children: 'Third button item', // eslint-disable-next-line no-alert onClick: () => alert( 'Third item clicked' ), }, + { + children: 'Anchor item', + as: 'a', + href: 'https://wordpress.org', + }, ] as ItemProps[] ).map( mapPropsToItem ), }; diff --git a/packages/components/src/item-group/styles.ts b/packages/components/src/item-group/styles.ts index 670fdf987dd932..95076f0bb4081d 100644 --- a/packages/components/src/item-group/styles.ts +++ b/packages/components/src/item-group/styles.ts @@ -6,34 +6,45 @@ import { css } from '@emotion/react'; /** * Internal dependencies */ -import { CONFIG, COLORS } from '../utils'; - -export const unstyledButton = css` - appearance: none; - border: 1px solid transparent; - cursor: pointer; - background: none; - text-align: start; - - svg, - path { - fill: currentColor; - } - - &:hover { - color: ${ COLORS.ui.theme }; - } - - &:focus-visible { - box-shadow: 0 0 0 var( --wp-admin-border-width-focus ) - var( - --wp-components-color-accent, - var( --wp-admin-theme-color, ${ COLORS.ui.theme } ) - ); - // Windows high contrast mode. - outline: 2px solid transparent; - } -`; +import { CONFIG, COLORS, font } from '../utils'; + +export const unstyledButton = ( as: 'a' | 'button' ) => { + return css` + font-size: ${ font( 'default.fontSize' ) }; + font-family: inherit; + appearance: none; + border: 1px solid transparent; + cursor: pointer; + background: none; + text-align: start; + text-decoration: ${ as === 'a' ? 'none' : undefined }; + + svg, + path { + fill: currentColor; + } + + &:hover { + color: ${ COLORS.ui.theme }; + } + + &:focus { + box-shadow: none; + outline: none; + } + + &:focus-visible { + box-shadow: 0 0 0 var( --wp-admin-border-width-focus ) + var( + --wp-components-color-accent, + var( --wp-admin-theme-color, ${ COLORS.ui.theme } ) + ); + // Windows high contrast mode. + outline: 2px solid transparent; + outline-offset: 0; + } + `; +}; export const itemWrapper = css` width: 100%; diff --git a/packages/components/src/navigator/navigator-provider/component.tsx b/packages/components/src/navigator/navigator-provider/component.tsx index 4c98db5b1a4d18..9bcffad6bc54bc 100644 --- a/packages/components/src/navigator/navigator-provider/component.tsx +++ b/packages/components/src/navigator/navigator-provider/component.tsx @@ -148,6 +148,7 @@ function UnconnectedNavigatorProvider( focusTargetSelector, isBack = false, skipFocus = false, + replace = false, ...restOptions } = options; @@ -172,34 +173,38 @@ function UnconnectedNavigatorProvider( skipFocus, }; - if ( prevLocationHistory.length < 1 ) { - return [ newLocation ]; + if ( prevLocationHistory.length === 0 ) { + return replace ? [] : [ newLocation ]; } - return [ - ...prevLocationHistory.slice( - prevLocationHistory.length > MAX_HISTORY_LENGTH - 1 - ? 1 - : 0, - -1 - ), - // Assign `focusTargetSelector` to the previous location in history - // (the one we just navigated from). - { - ...prevLocationHistory[ - prevLocationHistory.length - 1 - ], - focusTargetSelector, - }, - newLocation, - ]; + const newLocationHistory = prevLocationHistory.slice( + prevLocationHistory.length > MAX_HISTORY_LENGTH - 1 ? 1 : 0, + -1 + ); + + if ( ! replace ) { + newLocationHistory.push( + // Assign `focusTargetSelector` to the previous location in history + // (the one we just navigated from). + { + ...prevLocationHistory[ + prevLocationHistory.length - 1 + ], + focusTargetSelector, + } + ); + } + + newLocationHistory.push( newLocation ); + + return newLocationHistory; } ); }, [ goBack ] ); - const goToParent: NavigatorContextType[ 'goToParent' ] = - useCallback( () => { + const goToParent: NavigatorContextType[ 'goToParent' ] = useCallback( + ( options = {} ) => { const currentPath = currentLocationHistory.current[ currentLocationHistory.current.length - 1 @@ -214,8 +219,10 @@ function UnconnectedNavigatorProvider( if ( parentPath === undefined ) { return; } - goTo( parentPath, { isBack: true } ); - }, [ goTo ] ); + goTo( parentPath, { ...options, isBack: true } ); + }, + [ goTo ] + ); const navigatorContextValue: NavigatorContextType = useMemo( () => ( { diff --git a/packages/components/src/navigator/types.ts b/packages/components/src/navigator/types.ts index e638084e8376d5..557f8074fd42e2 100644 --- a/packages/components/src/navigator/types.ts +++ b/packages/components/src/navigator/types.ts @@ -14,8 +14,11 @@ export type NavigateOptions = { focusTargetSelector?: string; isBack?: boolean; skipFocus?: boolean; + replace?: boolean; }; +export type NavigateToParentOptions = Omit< NavigateOptions, 'isBack' >; + export type NavigatorLocation = NavigateOptions & { isInitial?: boolean; path?: string; @@ -28,7 +31,7 @@ export type Navigator = { params: MatchParams; goTo: ( path: string, options?: NavigateOptions ) => void; goBack: () => void; - goToParent: () => void; + goToParent: ( options?: NavigateToParentOptions ) => void; }; export type NavigatorContext = Navigator & { diff --git a/packages/components/src/placeholder/style.scss b/packages/components/src/placeholder/style.scss index 7fd9977e4645dc..df06969852fdae 100644 --- a/packages/components/src/placeholder/style.scss +++ b/packages/components/src/placeholder/style.scss @@ -161,6 +161,11 @@ padding: 0 $grid-unit-10 2px; } } + .components-placeholder__learn-more { + .components-external-link { + color: var(--wp-admin-theme-color); + } + } } diff --git a/packages/components/src/query-controls/index.native.js b/packages/components/src/query-controls/index.native.js index 6ba18f646146e6..51ba7cba690be5 100644 --- a/packages/components/src/query-controls/index.native.js +++ b/packages/components/src/query-controls/index.native.js @@ -84,6 +84,7 @@ const QueryControls = memo( ) } { onNumberOfItemsChange && ( ) => onChange( event.target.value ); diff --git a/packages/components/src/text-control/test/text-control.tsx b/packages/components/src/text-control/test/text-control.tsx new file mode 100644 index 00000000000000..fc048b93992f08 --- /dev/null +++ b/packages/components/src/text-control/test/text-control.tsx @@ -0,0 +1,61 @@ +/** + * External dependencies + */ +import { render, screen } from '@testing-library/react'; + +/** + * Internal dependencies + */ +import TextControl from '..'; + +const noop = () => {}; + +describe( 'TextControl', () => { + describe( 'When no ID prop is provided', () => { + it( 'should generate an ID', () => { + render( ); + + expect( screen.getByRole( 'textbox' ) ).toHaveAttribute( + 'id', + expect.stringMatching( /^inspector-text-control-/ ) + ); + } ); + + it( 'should be labelled correctly', () => { + const labelValue = 'Test Label'; + render( + + ); + + expect( + screen.getByRole( 'textbox', { name: labelValue } ) + ).toBeVisible(); + } ); + } ); + + describe( 'When an ID prop is provided', () => { + const id = 'test-id'; + + it( 'should use the passed ID prop if provided', () => { + render( ); + + expect( screen.getByRole( 'textbox' ) ).toHaveAttribute( 'id', id ); + } ); + + it( 'should be labelled correctly', () => { + const labelValue = 'Test Label'; + render( + + ); + + expect( + screen.getByRole( 'textbox', { name: labelValue } ) + ).toBeVisible(); + } ); + } ); +} ); diff --git a/packages/components/src/toolbar/toolbar-group/style.native.scss b/packages/components/src/toolbar/toolbar-group/style.native.scss index e218aa37363e37..f45797de3a625a 100644 --- a/packages/components/src/toolbar/toolbar-group/style.native.scss +++ b/packages/components/src/toolbar/toolbar-group/style.native.scss @@ -1,11 +1,10 @@ .container { flex-direction: row; - border-left-width: 1px; - border-color: #e9eff3; + border-color: $light-quaternary; padding-left: 5px; padding-right: 5px; } .containerDark { - border-color: #525354; + border-color: $dark-quaternary; } diff --git a/packages/components/src/toolbar/toolbar-group/toolbar-group-container.native.js b/packages/components/src/toolbar/toolbar-group/toolbar-group-container.native.js index 6eaf0dc0c3c74a..53c2c59bda9548 100644 --- a/packages/components/src/toolbar/toolbar-group/toolbar-group-container.native.js +++ b/packages/components/src/toolbar/toolbar-group/toolbar-group-container.native.js @@ -1,31 +1,26 @@ /** * External dependencies */ -import { View } from 'react-native'; +import { StyleSheet, View } from 'react-native'; /** * WordPress dependencies */ -import { withPreferredColorScheme } from '@wordpress/compose'; +import { usePreferredColorSchemeStyle } from '@wordpress/compose'; /** * Internal dependencies */ import styles from './style.scss'; -const ToolbarGroupContainer = ( { - getStylesFromColorScheme, - passedStyle, - children, -} ) => ( - - { children } - -); +const ToolbarGroupContainer = ( { passedStyle, children } ) => { + const groupStyles = [ + usePreferredColorSchemeStyle( styles.container, styles.containerDark ), + { borderLeftWidth: StyleSheet.hairlineWidth }, + passedStyle, + ]; -export default withPreferredColorScheme( ToolbarGroupContainer ); + return { children }; +}; + +export default ToolbarGroupContainer; diff --git a/packages/components/src/tooltip/README.md b/packages/components/src/tooltip/README.md index e4d030681240ca..101d642f39eb2e 100644 --- a/packages/components/src/tooltip/README.md +++ b/packages/components/src/tooltip/README.md @@ -28,7 +28,7 @@ The direction in which the tooltip should open relative to its parent node. Spec - Type: `String` - Required: No -- Default: `"top center"` +- Default: `"bottom"` ### children diff --git a/packages/compose/CHANGELOG.md b/packages/compose/CHANGELOG.md index dd09c0f94a2291..7750b279efdd75 100644 --- a/packages/compose/CHANGELOG.md +++ b/packages/compose/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 6.14.0 (2023-07-05) + ## 6.13.0 (2023-06-23) ## 6.12.0 (2023-06-07) diff --git a/packages/compose/package.json b/packages/compose/package.json index 6fc4d8c9db746b..af4d539bd978fd 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/compose", - "version": "6.13.0", + "version": "6.14.0", "description": "WordPress higher-order components (HOCs).", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/core-commands/CHANGELOG.md b/packages/core-commands/CHANGELOG.md index 7f958692b8b510..b0c207cf0fada0 100644 --- a/packages/core-commands/CHANGELOG.md +++ b/packages/core-commands/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 0.6.0 (2023-07-05) + ## 0.5.0 (2023-06-23) ## 0.4.0 (2023-06-07) diff --git a/packages/core-commands/package.json b/packages/core-commands/package.json index ed06ff22c3fd89..5eee67fca77f62 100644 --- a/packages/core-commands/package.json +++ b/packages/core-commands/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-commands", - "version": "0.5.0", + "version": "0.6.0", "description": "WordPress core reusable commands.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/core-commands/src/site-editor-navigation-commands.js b/packages/core-commands/src/site-editor-navigation-commands.js index 8f2003d0e08a27..3d551619d63b04 100644 --- a/packages/core-commands/src/site-editor-navigation-commands.js +++ b/packages/core-commands/src/site-editor-navigation-commands.js @@ -13,6 +13,7 @@ import { symbolFilled, styles, navigation, + symbol, } from '@wordpress/icons'; import { privateApis as routerPrivateApis } from '@wordpress/router'; import { getQueryArg, addQueryArgs, getPath } from '@wordpress/url'; @@ -199,8 +200,8 @@ function useSiteEditorBasicNavigationCommands() { result.push( { name: 'core/edit-site/open-template-parts', - label: __( 'Open library' ), - icon: symbolFilled, + label: __( 'Open patterns' ), + icon: symbol, callback: ( { close } ) => { const args = { path: '/patterns', diff --git a/packages/core-data/CHANGELOG.md b/packages/core-data/CHANGELOG.md index 5b6d9857dfdc3f..025827081db643 100644 --- a/packages/core-data/CHANGELOG.md +++ b/packages/core-data/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 6.14.0 (2023-07-05) + ## 6.13.0 (2023-06-23) ## 6.12.0 (2023-06-07) diff --git a/packages/core-data/package.json b/packages/core-data/package.json index a5b9572e275bcf..24b7869a3b0055 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-data", - "version": "6.13.0", + "version": "6.14.0", "description": "Access to and manipulation of core WordPress entities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index cfab95aae9f8fc..824e1159286ede 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -18,7 +18,6 @@ import { receiveItems, removeItems, receiveQueriedItems } from './queried-data'; import { getOrLoadEntitiesConfig, DEFAULT_ENTITY_KEY } from './entities'; import { createBatch } from './batch'; import { STORE_NAME } from './name'; -import { getUndoEdits, getRedoEdits } from './private-selectors'; /** * Returns an action object used in signalling that authors have been received. @@ -357,7 +356,7 @@ export const editEntityRecord = `The entity being edited (${ kind }, ${ name }) does not have a loaded config.` ); } - const { transientEdits = {}, mergedEdits = {} } = entityConfig; + const { mergedEdits = {} } = entityConfig; const record = select.getRawEntityRecord( kind, name, recordId ); const editedRecord = select.getEditedEntityRecord( kind, @@ -382,7 +381,6 @@ export const editEntityRecord = : value; return acc; }, {} ), - transientEdits, }; dispatch( { type: 'EDIT_ENTITY_RECORD', @@ -395,6 +393,7 @@ export const editEntityRecord = acc[ key ] = editedRecord[ key ]; return acc; }, {} ), + isCached: options.isCached, }, }, } ); @@ -407,8 +406,7 @@ export const editEntityRecord = export const undo = () => ( { select, dispatch } ) => { - // Todo: we shouldn't have to pass "root" here. - const undoEdit = select( ( state ) => getUndoEdits( state.root ) ); + const undoEdit = select.getUndoEdits(); if ( ! undoEdit ) { return; } @@ -425,8 +423,7 @@ export const undo = export const redo = () => ( { select, dispatch } ) => { - // Todo: we shouldn't have to pass "root" here. - const redoEdit = select( ( state ) => getRedoEdits( state.root ) ); + const redoEdit = select.getRedoEdits(); if ( ! redoEdit ) { return; } @@ -789,6 +786,22 @@ export const __experimentalSaveSpecifiedEntityEdits = editsToSave[ edit ] = edits[ edit ]; } } + + const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const entityConfig = configs.find( + ( config ) => config.kind === kind && config.name === name + ); + + const entityIdKey = entityConfig?.key || DEFAULT_ENTITY_KEY; + + // If a record key is provided then update the existing record. + // This necessitates providing `recordKey` to saveEntityRecord as part of the + // `record` argument (here called `editsToSave`) to stop that action creating + // a new record and instead cause it to update the existing record. + if ( recordId ) { + editsToSave[ entityIdKey ] = recordId; + } + return await dispatch.saveEntityRecord( kind, name, diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index 04bb4c21433e30..725ba9f99ae03c 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -5,9 +5,9 @@ import { createContext, useContext, useCallback, - useEffect, + useMemo, } from '@wordpress/element'; -import { useSelect, useDispatch, useRegistry } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { parse, __unstableSerializeAndClean } from '@wordpress/blocks'; import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; @@ -154,17 +154,16 @@ export function useEntityProp( kind, name, prop, _id ) { * @return {[WPBlock[], Function, Function]} The block array and setters. */ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { - const [ meta, updateMeta ] = useEntityProp( kind, name, 'meta', _id ); - const registry = useRegistry(); const providerId = useEntityId( kind, name ); const id = _id ?? providerId; - const { content, blocks } = useSelect( + const { content, editedBlocks, meta } = useSelect( ( select ) => { const { getEditedEntityRecord } = select( STORE_NAME ); const editedRecord = getEditedEntityRecord( kind, name, id ); return { - blocks: editedRecord.blocks, + editedBlocks: editedRecord.blocks, content: editedRecord.content, + meta: editedRecord.meta, }; }, [ kind, name, id ] @@ -172,29 +171,22 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { const { __unstableCreateUndoLevel, editEntityRecord } = useDispatch( STORE_NAME ); - useEffect( () => { - // Load the blocks from the content if not already in state - // Guard against other instances that might have - // set content to a function already or the blocks are already in state. - if ( content && typeof content !== 'function' && ! blocks ) { - const parsedContent = parse( content ); - editEntityRecord( - kind, - name, - id, - { - blocks: parsedContent, - }, - { undoIgnore: true } - ); + const blocks = useMemo( () => { + if ( editedBlocks ) { + return editedBlocks; } - }, [ content ] ); + + return content && typeof content !== 'function' + ? parse( content ) + : EMPTY_ARRAY; + }, [ editedBlocks, content ] ); const updateFootnotes = useCallback( ( _blocks ) => { - if ( ! meta ) return; + const output = { blocks: _blocks }; + if ( ! meta ) return output; // If meta.footnotes is empty, it means the meta is not registered. - if ( meta.footnotes === undefined ) return; + if ( meta.footnotes === undefined ) return output; const { getRichTextValues } = unlock( blockEditorPrivateApis ); const _content = getRichTextValues( _blocks ).join( '' ) || ''; @@ -216,7 +208,8 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { : []; const currentOrder = footnotes.map( ( fn ) => fn.id ); - if ( currentOrder.join( '' ) === newOrder.join( '' ) ) return; + if ( currentOrder.join( '' ) === newOrder.join( '' ) ) + return output; const newFootnotes = newOrder.map( ( fnId ) => @@ -227,6 +220,71 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { } ); + function updateAttributes( attributes ) { + attributes = { ...attributes }; + + for ( const key in attributes ) { + const value = attributes[ key ]; + + if ( Array.isArray( value ) ) { + attributes[ key ] = value.map( updateAttributes ); + continue; + } + + if ( typeof value !== 'string' ) { + continue; + } + + if ( value.indexOf( 'data-fn' ) === -1 ) { + continue; + } + + // When we store rich text values, this would no longer + // require a regex. + const regex = + /(]+data-fn="([^"]+)"[^>]*>]*>)[\d*]*<\/a><\/sup>/g; + + attributes[ key ] = value.replace( + regex, + ( match, opening, fnId ) => { + const index = newOrder.indexOf( fnId ); + return `${ opening }${ index + 1 }`; + } + ); + + const compatRegex = + /]+data-fn="([^"]+)"[^>]*>\*<\/a>/g; + + attributes[ key ] = attributes[ key ].replace( + compatRegex, + ( match, fnId ) => { + const index = newOrder.indexOf( fnId ); + return `${ + index + 1 + }`; + } + ); + } + + return attributes; + } + + function updateBlocksAttributes( __blocks ) { + return __blocks.map( ( block ) => { + return { + ...block, + attributes: updateAttributes( block.attributes ), + innerBlocks: updateBlocksAttributes( + block.innerBlocks + ), + }; + } ); + } + + // We need to go through all block attributs deeply and update the + // footnote anchor numbering (textContent) to match the new order. + const newBlocks = updateBlocksAttributes( _blocks ); + oldFootnotes = { ...oldFootnotes, ...footnotes.reduce( ( acc, fn ) => { @@ -237,49 +295,58 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { }, {} ), }; - updateMeta( { - ...meta, - footnotes: JSON.stringify( newFootnotes ), - } ); + return { + meta: { + ...meta, + footnotes: JSON.stringify( newFootnotes ), + }, + blocks: newBlocks, + }; }, - [ meta, updateMeta ] + [ meta ] ); const onChange = useCallback( ( newBlocks, options ) => { - const { selection } = options; - const edits = { blocks: newBlocks, selection }; - - const noChange = blocks === edits.blocks; + const noChange = blocks === newBlocks; if ( noChange ) { return __unstableCreateUndoLevel( kind, name, id ); } + const { selection } = options; // 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 = [] } ) => - __unstableSerializeAndClean( blocksForSerialization ); + const edits = { + selection, + content: ( { blocks: blocksForSerialization = [] } ) => + __unstableSerializeAndClean( blocksForSerialization ), + ...updateFootnotes( newBlocks ), + }; - registry.batch( () => { - updateFootnotes( edits.blocks ); - editEntityRecord( kind, name, id, edits ); - } ); + editEntityRecord( kind, name, id, edits, { isCached: false } ); }, - [ kind, name, id, blocks, updateFootnotes ] + [ + kind, + name, + id, + blocks, + updateFootnotes, + __unstableCreateUndoLevel, + editEntityRecord, + ] ); const onInput = useCallback( ( newBlocks, options ) => { const { selection } = options; - const edits = { blocks: newBlocks, selection }; - registry.batch( () => { - updateFootnotes( edits.blocks ); - editEntityRecord( kind, name, id, edits ); - } ); + const footnotesChanges = updateFootnotes( newBlocks ); + const edits = { selection, ...footnotesChanges }; + + editEntityRecord( kind, name, id, edits, { isCached: true } ); }, - [ kind, name, id, updateFootnotes ] + [ kind, name, id, updateFootnotes, editEntityRecord ] ); - return [ blocks ?? EMPTY_ARRAY, onInput, onChange ]; + return [ blocks, onInput, onChange ]; } diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index 98509d9f0383ba..5b42b6731716ff 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -8,13 +8,13 @@ import { createReduxStore, register } from '@wordpress/data'; */ import reducer from './reducer'; import * as selectors from './selectors'; +import * as privateSelectors from './private-selectors'; import * as actions from './actions'; import * as resolvers from './resolvers'; import createLocksActions from './locks/actions'; import { rootEntitiesConfig, getMethodName } from './entities'; import { STORE_NAME } from './name'; import { unlock } from './private-apis'; -import { getNavigationFallbackId } from './private-selectors'; // The entity selectors/resolvers and actions are shortcuts to their generic equivalents // (getEntityRecord, getEntityRecords, updateEntityRecord, updateEntityRecords) @@ -64,9 +64,7 @@ const storeConfig = () => ( { * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/data/README.md#createReduxStore */ export const store = createReduxStore( STORE_NAME, storeConfig() ); -unlock( store ).registerPrivateSelectors( { - getNavigationFallbackId, -} ); +unlock( store ).registerPrivateSelectors( privateSelectors ); register( store ); // Register store after unlocking private selectors to allow resolvers to use them. export { default as EntityProvider } from './entity-provider'; diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index b7dd9d73df15a7..20755dad4be8d2 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -439,7 +439,7 @@ export const entities = ( state = {}, action ) => { * * @property {number} list The undo stack. * @property {number} offset Where in the undo stack we are. - * @property {Object} cache Cache of unpersisted transient edits. + * @property {Object} cache Cache of unpersisted edits. */ /** @typedef {Array & UndoStateMeta} UndoState */ @@ -543,10 +543,6 @@ export function undo( state = UNDO_INITIAL_STATE, action ) { return state; } - const isCachedChange = Object.keys( action.edits ).every( - ( key ) => action.transientEdits[ key ] - ); - const edits = Object.keys( action.edits ).map( ( key ) => { return { kind: action.kind, @@ -558,7 +554,7 @@ export function undo( state = UNDO_INITIAL_STATE, action ) { }; } ); - if ( isCachedChange ) { + if ( action.meta.undo.isCached ) { return { ...state, cache: edits.reduce( appendEditToStack, state.cache ), diff --git a/packages/core-data/src/test/reducer.js b/packages/core-data/src/test/reducer.js index 4f7d9b9c0d2aec..7fac52c33c4b36 100644 --- a/packages/core-data/src/test/reducer.js +++ b/packages/core-data/src/test/reducer.js @@ -155,19 +155,21 @@ describe( 'undo', () => { from, to, } ); - const createNextEditAction = ( edits, transientEdits = {} ) => { + const createNextEditAction = ( edits, isCached ) => { let action = { kind: 'someKind', name: 'someName', recordId: 'someRecordId', edits, - transientEdits, }; action = { type: 'EDIT_ENTITY_RECORD', ...action, meta: { - undo: { edits: lastValues }, + undo: { + isCached, + edits: lastValues, + }, }, }; lastValues = { ...lastValues, ...edits }; @@ -303,10 +305,7 @@ describe( 'undo', () => { it( 'handles flattened undos/redos', () => { undoState = createNextUndoState(); undoState = createNextUndoState( { value: 1 } ); - undoState = createNextUndoState( - { transientValue: 2 }, - { transientValue: true } - ); + undoState = createNextUndoState( { transientValue: 2 }, true ); undoState = createNextUndoState( { value: 3 } ); expectedUndoState.list.push( [ @@ -335,10 +334,7 @@ describe( 'undo', () => { // Check that transient edits are merged into the last // edits. - undoState = createNextUndoState( - { transientValue: 2 }, - { transientValue: true } - ); + undoState = createNextUndoState( { transientValue: 2 }, true ); undoState = createNextUndoState( 'isCreate' ); expectedUndoState.list[ expectedUndoState.list.length - 1 ].push( createExpectedDiff( 'transientValue', { from: undefined, to: 2 } ) @@ -359,10 +355,7 @@ describe( 'undo', () => { it( 'explicitly creates an undo level when undoing while there are pending transient edits', () => { undoState = createNextUndoState(); undoState = createNextUndoState( { value: 1 } ); - undoState = createNextUndoState( - { transientValue: 2 }, - { transientValue: true } - ); + undoState = createNextUndoState( { transientValue: 2 }, true ); undoState = createNextUndoState( 'isUndo' ); expectedUndoState.list.push( [ createExpectedDiff( 'value', { from: undefined, to: 1 } ), diff --git a/packages/core-data/src/utils/set-nested-value.js b/packages/core-data/src/utils/set-nested-value.js index cb7db8a04b4b07..e90bf23e4dad8e 100644 --- a/packages/core-data/src/utils/set-nested-value.js +++ b/packages/core-data/src/utils/set-nested-value.js @@ -10,6 +10,8 @@ * * @see https://lodash.com/docs/4.17.15#set * + * @todo Needs to be deduplicated with its copy in `@wordpress/edit-site`. + * * @param {Object} object Object to modify * @param {Array} path Path of the property to set. * @param {*} value Value to set. diff --git a/packages/create-block-interactive-template/.npmrc b/packages/create-block-interactive-template/.npmrc new file mode 100644 index 00000000000000..43c97e719a5a82 --- /dev/null +++ b/packages/create-block-interactive-template/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/create-block-interactive-template/CHANGELOG.md b/packages/create-block-interactive-template/CHANGELOG.md new file mode 100644 index 00000000000000..a79948001de14a --- /dev/null +++ b/packages/create-block-interactive-template/CHANGELOG.md @@ -0,0 +1 @@ + diff --git a/packages/create-block-interactive-template/README.md b/packages/create-block-interactive-template/README.md new file mode 100644 index 00000000000000..cc0530c0630549 --- /dev/null +++ b/packages/create-block-interactive-template/README.md @@ -0,0 +1,19 @@ +# Create Block Interactive Template + +This is a template for [`@wordpress/create-block`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/create-block/README.md) to create interactive blocks + +## Usage + +This block template can be used by running the following command: + +```bash +npx @wordpress/create-block --template @wordpress/create-block-interactive-template +``` + +## Contributing to this package + +This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects. + +To find out more about contributing to this package or Gutenberg as a whole, please read the project's main [contributor guide](https://github.com/WordPress/gutenberg/tree/HEAD/CONTRIBUTING.md). + +

      Code is Poetry.

      diff --git a/packages/create-block-interactive-template/block-templates/README.md.mustache b/packages/create-block-interactive-template/block-templates/README.md.mustache new file mode 100644 index 00000000000000..728f37b8c0e39f --- /dev/null +++ b/packages/create-block-interactive-template/block-templates/README.md.mustache @@ -0,0 +1,16 @@ +# Interactive Block + +> **Warning** +> **This block requires Gutenberg 16.2 or superior to work**. The Interactivity API is, at the moment, not part of WordPress Core as it is still very experimental, and very likely to change. + +> **Note** +> This block uses the API shared at [Proposal: The Interactivity API – A better developer experience in building interactive blocks](https://make.wordpress.org/core/2023/03/30/proposal-the-interactivity-api-a-better-developer-experience-in-building-interactive-blocks/). + +{{#isBasicVariant}} +This block has been created with the `create-block-interactive-template` and shows a basic structure of an interactive block that uses the Interactivity API. +{{/isBasicVariant}} + +Check the following resources for more info about the Interactivity API: +- [`@wordpress/interactivity` package](https://github.com/WordPress/gutenberg/blob/trunk/packages/interactivity/README.md) +- [Proposal: The Interactivity API – A better developer experience in building interactive blocks](https://make.wordpress.org/core/2023/03/30/proposal-the-interactivity-api-a-better-developer-experience-in-building-interactive-blocks/) +- [“Interactivity API” category](https://github.com/WordPress/gutenberg/discussions/categories/interactivity-api) in Gutenberg repo discussions \ No newline at end of file diff --git a/packages/create-block-interactive-template/block-templates/edit.js.mustache b/packages/create-block-interactive-template/block-templates/edit.js.mustache new file mode 100644 index 00000000000000..1a0aeeac8d6979 --- /dev/null +++ b/packages/create-block-interactive-template/block-templates/edit.js.mustache @@ -0,0 +1,36 @@ +/** + * Retrieves the translation of text. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/ + */ +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/reference-guides/packages/packages-block-editor/#useblockprops + */ +import { useBlockProps } from '@wordpress/block-editor'; + +/** + * The edit function describes the structure of your block in the context of the + * editor. This represents what the editor will render when the block is used. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit + * + * @param {Object} props Properties passed to the function. + * @param {Object} props.attributes Available block attributes. + * @param {Function} props.setAttributes Function that updates individual attributes. + * + * @return {WPElement} Element to render. + */ +export default function Edit( { attributes, setAttributes } ) { + const blockProps = useBlockProps(); + + return ( +

      + { __( '{{title}} – hello from the editor!', '{{textdomain}}' ) } +

      + ); +} diff --git a/packages/create-block-interactive-template/block-templates/editor.scss.mustache b/packages/create-block-interactive-template/block-templates/editor.scss.mustache new file mode 100644 index 00000000000000..bdb19abec84212 --- /dev/null +++ b/packages/create-block-interactive-template/block-templates/editor.scss.mustache @@ -0,0 +1,12 @@ +/** + * The following styles get applied inside the editor only. + * + * Replace them with your own styles or remove the file completely. + */ + +.wp-block-{{namespace}}-{{slug}} input[type="text"] { + font-size: 1em; + color: inherit; + background: inherit; + border: 0; +} diff --git a/packages/create-block-interactive-template/block-templates/index.js.mustache b/packages/create-block-interactive-template/block-templates/index.js.mustache new file mode 100644 index 00000000000000..cf40358217c76d --- /dev/null +++ b/packages/create-block-interactive-template/block-templates/index.js.mustache @@ -0,0 +1,43 @@ +/** + * Registers a new block provided a unique name and an object defining its behavior. + * + * @see https://developer.wordpress.org/block-editor/developers/block-api/#registering-a-block + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files. + * All files containing `style` keyword are bundled together. The code used + * gets applied both to the front of your site and to the editor. All other files + * get applied to the editor only. + * + * @see https://www.npmjs.com/package/@wordpress/scripts#using-css + */ +import './style.scss'; +import './editor.scss'; + +/** + * Internal dependencies + */ +import Edit from './edit'; +import metadata from './block.json'; + +/** + * Every block starts by registering a new block type definition. + * + * @see https://developer.wordpress.org/block-editor/developers/block-api/#registering-a-block + */ +registerBlockType( metadata.name, { + /** + * Used to construct a preview for the block to be shown in the block inserter. + */ + example: { + attributes: { + message: '{{title}}', + }, + }, + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/packages/create-block-interactive-template/block-templates/render.php.mustache b/packages/create-block-interactive-template/block-templates/render.php.mustache new file mode 100644 index 00000000000000..ba791783e6edef --- /dev/null +++ b/packages/create-block-interactive-template/block-templates/render.php.mustache @@ -0,0 +1,33 @@ +{{#isBasicVariant}} + + +
      + data-wp-interactive + data-wp-context='{ "{{namespace}}": { "isOpen": false } }' + data-wp-effect="effects.{{namespace}}.logIsOpen" +> + + +

      + +

      +
      +{{/isBasicVariant}} \ No newline at end of file diff --git a/packages/create-block-interactive-template/block-templates/style.scss.mustache b/packages/create-block-interactive-template/block-templates/style.scss.mustache new file mode 100644 index 00000000000000..1c73fa1c38ff94 --- /dev/null +++ b/packages/create-block-interactive-template/block-templates/style.scss.mustache @@ -0,0 +1,12 @@ +/** + * The following styles get applied both on the front of your site + * and in the editor. + * + * Replace them with your own styles or remove the file completely. + */ + +.wp-block-{{namespace}}-{{slug}} { + font-size: 1em; + background: #ffff001a; + padding: 1em; +} diff --git a/packages/create-block-interactive-template/block-templates/view.js.mustache b/packages/create-block-interactive-template/block-templates/view.js.mustache new file mode 100644 index 00000000000000..85d74fec190ba5 --- /dev/null +++ b/packages/create-block-interactive-template/block-templates/view.js.mustache @@ -0,0 +1,26 @@ +{{#isBasicVariant}} + +/** + * WordPress dependencies + */ +import { store } from "@wordpress/interactivity"; + +store( { + actions: { + '{{namespace}}': { + toggle: ( { context } ) => { + context[ '{{namespace}}' ].isOpen = !context[ '{{namespace}}' ].isOpen; + }, + }, + }, + effects: { + '{{namespace}}': { + logIsOpen: ( { context } ) => { + // Log the value of `isOpen` each time it changes. + console.log( `Is open: ${ context[ '{{namespace}}' ].isOpen }` ); + }, + }, + }, +} ); + +{{/isBasicVariant}} \ No newline at end of file diff --git a/packages/create-block-interactive-template/index.js b/packages/create-block-interactive-template/index.js new file mode 100644 index 00000000000000..a52475ed7a79e5 --- /dev/null +++ b/packages/create-block-interactive-template/index.js @@ -0,0 +1,24 @@ +/** + * External dependencies + */ +const { join } = require( 'path' ); + +module.exports = { + defaultValues: { + slug: 'example-interactive', + title: 'Example Interactive', + description: 'An interactive block with the Interactivity API', + dashicon: 'media-interactive', + npmDependencies: [ '@wordpress/interactivity' ], + supports: { + interactivity: true, + }, + render: 'file:./render.php', + viewScript: 'file:./view.js', + }, + variants: { + basic: {}, + }, + pluginTemplatesPath: join( __dirname, 'plugin-templates' ), + blockTemplatesPath: join( __dirname, 'block-templates' ), +}; diff --git a/packages/create-block-interactive-template/package.json b/packages/create-block-interactive-template/package.json new file mode 100644 index 00000000000000..1638b33e648478 --- /dev/null +++ b/packages/create-block-interactive-template/package.json @@ -0,0 +1,25 @@ +{ + "name": "@wordpress/create-block-interactive-template", + "version": "1.0.0", + "description": "Template for @wordpress/create-block to create interactive blocks with the Interactivity API.", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress", + "create block", + "block template", + "Interactivity API" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/docs/getting-started/create-block", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/create-block-interactive-template" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/create-block-interactive-template/plugin-templates/$slug.php.mustache b/packages/create-block-interactive-template/plugin-templates/$slug.php.mustache new file mode 100644 index 00000000000000..2322881ab0d710 --- /dev/null +++ b/packages/create-block-interactive-template/plugin-templates/$slug.php.mustache @@ -0,0 +1,43 @@ + !! value ) ), diff --git a/packages/create-block/lib/scaffold.js b/packages/create-block/lib/scaffold.js index cdeaf85a97bb4a..a8d9c3859e20d3 100644 --- a/packages/create-block/lib/scaffold.js +++ b/packages/create-block/lib/scaffold.js @@ -44,6 +44,7 @@ module.exports = async ( editorStyle, style, render, + viewScript, variantVars, customPackageJSON, customBlockJSON, @@ -103,6 +104,7 @@ module.exports = async ( editorStyle, style, render, + viewScript, customPackageJSON, customBlockJSON, ...variantVars, diff --git a/packages/create-block/lib/templates.js b/packages/create-block/lib/templates.js index 018385c9fde82c..4ce898617adde3 100644 --- a/packages/create-block/lib/templates.js +++ b/packages/create-block/lib/templates.js @@ -33,6 +33,7 @@ const predefinedPluginTemplates = { editorScript: null, editorStyle: null, style: null, + viewScript: 'file:./view.js', }, templatesPath: join( __dirname, 'templates', 'es5' ), variants: { @@ -53,6 +54,7 @@ const predefinedPluginTemplates = { supports: { html: false, }, + viewScript: 'file:./view.js', }, variants: { static: {}, diff --git a/packages/create-block/lib/templates/block/view.js.mustache b/packages/create-block/lib/templates/block/view.js.mustache new file mode 100644 index 00000000000000..baff12a165cd9a --- /dev/null +++ b/packages/create-block/lib/templates/block/view.js.mustache @@ -0,0 +1,3 @@ +/* eslint-disable no-console */ +console.log("Hello World! (from {{namespace}}-{{slug}} block)"); +/* eslint-enable no-console */ diff --git a/packages/create-block/package.json b/packages/create-block/package.json index 8a8bae31a334ec..5f8885ec0375b1 100644 --- a/packages/create-block/package.json +++ b/packages/create-block/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/create-block", - "version": "4.20.0", + "version": "4.21.0", "description": "Generates PHP, JS and CSS code for registering a block for a WordPress plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/customize-widgets/CHANGELOG.md b/packages/customize-widgets/CHANGELOG.md index 3eec6f26451340..45a77305107b36 100644 --- a/packages/customize-widgets/CHANGELOG.md +++ b/packages/customize-widgets/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.14.0 (2023-07-05) + ## 4.13.0 (2023-06-23) ## 4.12.0 (2023-06-07) diff --git a/packages/customize-widgets/package.json b/packages/customize-widgets/package.json index a9316080319531..96e313d27b9d38 100644 --- a/packages/customize-widgets/package.json +++ b/packages/customize-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/customize-widgets", - "version": "4.13.0", + "version": "4.14.0", "description": "Widgets blocks in Customizer Module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data-controls/CHANGELOG.md b/packages/data-controls/CHANGELOG.md index 57c16160b19eb4..b2d3fc117207bc 100644 --- a/packages/data-controls/CHANGELOG.md +++ b/packages/data-controls/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.6.0 (2023-07-05) + ## 3.5.0 (2023-06-23) ## 3.4.0 (2023-06-07) diff --git a/packages/data-controls/package.json b/packages/data-controls/package.json index 277e13e080c7a1..dbe372d1ee515c 100644 --- a/packages/data-controls/package.json +++ b/packages/data-controls/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data-controls", - "version": "3.5.0", + "version": "3.6.0", "description": "A set of common controls for the @wordpress/data api.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index 6b1fb31679157d..54d18172e42b3e 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 9.7.0 (2023-07-05) + ## 9.6.0 (2023-06-23) ## 9.5.0 (2023-06-07) diff --git a/packages/data/package.json b/packages/data/package.json index b151115dca76d0..810d48c7e7a10e 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data", - "version": "9.6.0", + "version": "9.7.0", "description": "Data module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data/src/registry.js b/packages/data/src/registry.js index d193a50cbc392d..02a0b19136a3e0 100644 --- a/packages/data/src/registry.js +++ b/packages/data/src/registry.js @@ -314,6 +314,12 @@ export function createRegistry( storeConfigs = {}, parent = null ) { } function batch( callback ) { + // If we're already batching, just call the callback. + if ( emitter.isPaused ) { + callback(); + return; + } + emitter.pause(); Object.values( stores ).forEach( ( store ) => store.emitter.pause() ); callback(); diff --git a/packages/data/src/test/registry.js b/packages/data/src/test/registry.js index b9288eae821d8a..df9cb774dfc8cf 100644 --- a/packages/data/src/test/registry.js +++ b/packages/data/src/test/registry.js @@ -734,6 +734,27 @@ describe( 'createRegistry', () => { unsubscribe(); expect( listener2 ).toHaveBeenCalledTimes( 1 ); } ); + + it( 'should support nested batches', () => { + const store = registry.registerStore( 'myAwesomeReducer', { + reducer: ( state = 0 ) => state + 1, + } ); + const listener = jest.fn(); + subscribeWithUnsubscribe( listener ); + + registry.batch( () => {} ); + expect( listener ).not.toHaveBeenCalled(); + + registry.batch( () => { + store.dispatch( { type: 'dummy' } ); + registry.batch( () => { + store.dispatch( { type: 'dummy' } ); + store.dispatch( { type: 'dummy' } ); + } ); + store.dispatch( { type: 'dummy' } ); + } ); + expect( listener ).toHaveBeenCalledTimes( 1 ); + } ); } ); describe( 'use', () => { diff --git a/packages/date/CHANGELOG.md b/packages/date/CHANGELOG.md index a20b8a26ace6c4..a81474839cb264 100644 --- a/packages/date/CHANGELOG.md +++ b/packages/date/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.37.0 (2023-07-05) + ## 4.36.0 (2023-06-23) ## 4.35.0 (2023-06-07) diff --git a/packages/date/package.json b/packages/date/package.json index f371b82c336035..436c85cd27da05 100644 --- a/packages/date/package.json +++ b/packages/date/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/date", - "version": "4.36.0", + "version": "4.37.0", "description": "Date module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md index d67b4a45698220..74fbd51bb0f36a 100644 --- a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md +++ b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.20.0 (2023-07-05) + ## 4.19.0 (2023-06-23) ## 4.18.0 (2023-06-07) diff --git a/packages/dependency-extraction-webpack-plugin/package.json b/packages/dependency-extraction-webpack-plugin/package.json index 59f312b0fffd10..6695e5e7f6002d 100644 --- a/packages/dependency-extraction-webpack-plugin/package.json +++ b/packages/dependency-extraction-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dependency-extraction-webpack-plugin", - "version": "4.19.0", + "version": "4.20.0", "description": "Extract WordPress script dependencies from webpack bundles.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", 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 85683906793548..0e4ff9e63a10a6 100644 --- a/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap +++ b/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap @@ -32,7 +32,7 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`combine-assets\` should pro `; exports[`DependencyExtractionWebpackPlugin Webpack \`dynamic-import\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('wp-blob'), 'version' => 'c8be4fceac30d1d00ca7'); +" array('wp-blob'), 'version' => 'b0e5d8b4c38533765be8'); " `; @@ -50,7 +50,7 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`dynamic-import\` should pro `; exports[`DependencyExtractionWebpackPlugin Webpack \`function-output-filename\` should produce expected output: Asset file 'chunk--main--main.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '9b7ebe61044661fdabda'); +" array('lodash', 'wp-blob'), 'version' => 'fc2d750fc9e08c5749db'); " `; @@ -96,7 +96,7 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`has-extension-suffix\` shou `; exports[`DependencyExtractionWebpackPlugin Webpack \`no-default\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array(), 'version' => 'f7e2cb527e601f74f8bd'); +" array(), 'version' => '43880e6c42e7c39fcdf1'); " `; @@ -110,7 +110,7 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`no-deps\` should produce ex exports[`DependencyExtractionWebpackPlugin Webpack \`no-deps\` should produce expected output: External modules should match snapshot 1`] = `[]`; exports[`DependencyExtractionWebpackPlugin Webpack \`option-function-output-filename\` should produce expected output: Asset file 'chunk--main--main.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '9b7ebe61044661fdabda'); +" array('lodash', 'wp-blob'), 'version' => 'fc2d750fc9e08c5749db'); " `; @@ -133,7 +133,7 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`option-function-output-file `; exports[`DependencyExtractionWebpackPlugin Webpack \`option-output-filename\` should produce expected output: Asset file 'main-foo.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '9b7ebe61044661fdabda'); +" array('lodash', 'wp-blob'), 'version' => 'fc2d750fc9e08c5749db'); " `; @@ -155,7 +155,7 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`option-output-filename\` sh ] `; -exports[`DependencyExtractionWebpackPlugin Webpack \`output-format-json\` should produce expected output: Asset file 'main.asset.json' should match snapshot 1`] = `"{"dependencies":["lodash"],"version":"4c42b9646049ad2e9438"}"`; +exports[`DependencyExtractionWebpackPlugin Webpack \`output-format-json\` should produce expected output: Asset file 'main.asset.json' should match snapshot 1`] = `"{"dependencies":["lodash"],"version":"7bd48470d799a795bf9a"}"`; exports[`DependencyExtractionWebpackPlugin Webpack \`output-format-json\` should produce expected output: External modules should match snapshot 1`] = ` [ @@ -207,17 +207,17 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`overrides\` should produce `; exports[`DependencyExtractionWebpackPlugin Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'a.asset.php' should match snapshot 1`] = ` -" array('wp-blob'), 'version' => '09a0c551770a351c5ca7'); +" array('wp-blob'), 'version' => 'd3cda564b538b44d38ef'); " `; exports[`DependencyExtractionWebpackPlugin Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'b.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => 'c9f00d690a9f72438910'); +" array('lodash', 'wp-blob'), 'version' => '420d636da562e71648f7'); " `; exports[`DependencyExtractionWebpackPlugin Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'runtime.asset.php' should match snapshot 1`] = ` -" array(), 'version' => '46ea0ff11ac53fa5e88b'); +" array(), 'version' => '66079b05b32ae1e16886'); " `; @@ -240,7 +240,7 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`runtime-chunk-single\` shou `; exports[`DependencyExtractionWebpackPlugin Webpack \`style-imports\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => 'd8c0ee89d933a3809c0e'); +" array('lodash', 'wp-blob'), 'version' => '4c661914a4e4d80b8a0b'); " `; @@ -263,7 +263,7 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`style-imports\` should prod `; exports[`DependencyExtractionWebpackPlugin Webpack \`wordpress\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '9b7ebe61044661fdabda'); +" array('lodash', 'wp-blob'), 'version' => 'fc2d750fc9e08c5749db'); " `; diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/index.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/index.js index 917b4cd7e204bf..4545bcd0c19bc6 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/index.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/index.js @@ -6,6 +6,7 @@ import { isBlobURL } from '@wordpress/blob'; /** * External dependencies */ +// eslint-disable-next-line no-restricted-imports import _ from 'lodash'; _.isEmpty( isBlobURL( '' ) ); diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/no-default/index.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/no-default/index.js index 612e420c2a6c4d..f9eb6f42f0a2dc 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/no-default/index.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/no-default/index.js @@ -1,6 +1,7 @@ /** * External dependencies */ +// eslint-disable-next-line no-restricted-imports import _ from 'lodash'; _.map( [], _.identity ); diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/option-function-output-filename/index.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/option-function-output-filename/index.js index 917b4cd7e204bf..4545bcd0c19bc6 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/option-function-output-filename/index.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/option-function-output-filename/index.js @@ -6,6 +6,7 @@ import { isBlobURL } from '@wordpress/blob'; /** * External dependencies */ +// eslint-disable-next-line no-restricted-imports import _ from 'lodash'; _.isEmpty( isBlobURL( '' ) ); diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/option-output-filename/index.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/option-output-filename/index.js index 917b4cd7e204bf..4545bcd0c19bc6 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/option-output-filename/index.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/option-output-filename/index.js @@ -6,6 +6,7 @@ import { isBlobURL } from '@wordpress/blob'; /** * External dependencies */ +// eslint-disable-next-line no-restricted-imports import _ from 'lodash'; _.isEmpty( isBlobURL( '' ) ); diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/output-format-json/index.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/output-format-json/index.js index 612e420c2a6c4d..f9eb6f42f0a2dc 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/output-format-json/index.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/output-format-json/index.js @@ -1,6 +1,7 @@ /** * External dependencies */ +// eslint-disable-next-line no-restricted-imports import _ from 'lodash'; _.map( [], _.identity ); diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/runtime-chunk-single/b.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/runtime-chunk-single/b.js index 917b4cd7e204bf..4545bcd0c19bc6 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/runtime-chunk-single/b.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/runtime-chunk-single/b.js @@ -6,6 +6,7 @@ import { isBlobURL } from '@wordpress/blob'; /** * External dependencies */ +// eslint-disable-next-line no-restricted-imports import _ from 'lodash'; _.isEmpty( isBlobURL( '' ) ); diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/style-imports/index.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/style-imports/index.js index df02e0b35e6f8e..b4e8cb76d1fa56 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/style-imports/index.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/style-imports/index.js @@ -6,6 +6,7 @@ import { isBlobURL } from '@wordpress/blob'; /** * External dependencies */ +// eslint-disable-next-line no-restricted-imports import _ from 'lodash'; /** diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress/index.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress/index.js index 917b4cd7e204bf..4545bcd0c19bc6 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress/index.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress/index.js @@ -6,6 +6,7 @@ import { isBlobURL } from '@wordpress/blob'; /** * External dependencies */ +// eslint-disable-next-line no-restricted-imports import _ from 'lodash'; _.isEmpty( isBlobURL( '' ) ); diff --git a/packages/deprecated/CHANGELOG.md b/packages/deprecated/CHANGELOG.md index 7d4bd62d699f86..ae4cc1fccc3e1d 100644 --- a/packages/deprecated/CHANGELOG.md +++ b/packages/deprecated/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.37.0 (2023-07-05) + ## 3.36.0 (2023-06-23) ## 3.35.0 (2023-06-07) diff --git a/packages/deprecated/package.json b/packages/deprecated/package.json index c59a1acaa8a054..09a05bef90c30a 100644 --- a/packages/deprecated/package.json +++ b/packages/deprecated/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/deprecated", - "version": "3.36.0", + "version": "3.37.0", "description": "Deprecation utility for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/docgen/CHANGELOG.md b/packages/docgen/CHANGELOG.md index 93b3f41495b840..7cc252c98b0b0f 100644 --- a/packages/docgen/CHANGELOG.md +++ b/packages/docgen/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.46.0 (2023-07-05) + ## 1.45.0 (2023-06-23) ## 1.44.0 (2023-06-07) diff --git a/packages/docgen/package.json b/packages/docgen/package.json index 9809d771b596d3..ee480b67454dd1 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/docgen", - "version": "1.45.0", + "version": "1.46.0", "description": "Autogenerate public API documentation from exports and JSDoc comments.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dom-ready/CHANGELOG.md b/packages/dom-ready/CHANGELOG.md index d270c829083ef3..fa80e8aa49336d 100644 --- a/packages/dom-ready/CHANGELOG.md +++ b/packages/dom-ready/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.37.0 (2023-07-05) + ## 3.36.0 (2023-06-23) ## 3.35.0 (2023-06-07) diff --git a/packages/dom-ready/package.json b/packages/dom-ready/package.json index d839cc32a3c908..d813006f11e4e9 100644 --- a/packages/dom-ready/package.json +++ b/packages/dom-ready/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom-ready", - "version": "3.36.0", + "version": "3.37.0", "description": "Execute callback after the DOM is loaded.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dom/CHANGELOG.md b/packages/dom/CHANGELOG.md index 7ca2d79a9cdd87..542f2f12b91420 100644 --- a/packages/dom/CHANGELOG.md +++ b/packages/dom/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.37.0 (2023-07-05) + ## 3.36.0 (2023-06-23) ## 3.35.0 (2023-06-07) diff --git a/packages/dom/package.json b/packages/dom/package.json index 6475b8844783fa..87bdd70de09d74 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom", - "version": "3.36.0", + "version": "3.37.0", "description": "DOM utilities module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-test-utils-playwright/CHANGELOG.md b/packages/e2e-test-utils-playwright/CHANGELOG.md index 157e3e21cbf631..7f55bbad7c8186 100644 --- a/packages/e2e-test-utils-playwright/CHANGELOG.md +++ b/packages/e2e-test-utils-playwright/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 0.5.0 (2023-07-05) + ## 0.4.0 (2023-06-23) ## 0.3.0 (2023-06-07) diff --git a/packages/e2e-test-utils-playwright/package.json b/packages/e2e-test-utils-playwright/package.json index ab82d133566276..5320239223ba57 100644 --- a/packages/e2e-test-utils-playwright/package.json +++ b/packages/e2e-test-utils-playwright/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-test-utils-playwright", - "version": "0.4.0", + "version": "0.5.0", "description": "End-To-End (E2E) test utils for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-test-utils-playwright/src/admin/create-new-post.js b/packages/e2e-test-utils-playwright/src/admin/create-new-post.js index 5d73157d3c02de..6f31f43355f67a 100644 --- a/packages/e2e-test-utils-playwright/src/admin/create-new-post.js +++ b/packages/e2e-test-utils-playwright/src/admin/create-new-post.js @@ -13,6 +13,7 @@ import { addQueryArgs } from '@wordpress/url'; * @param {string} [object.content] Content of the new post. * @param {string} [object.excerpt] Excerpt of the new post. * @param {boolean} [object.showWelcomeGuide] Whether to show the welcome guide. + * @param {boolean} [object.legacyCanvas] Whether the non-iframed editor canvas is awaited. */ export async function createNewPost( { postType, @@ -20,6 +21,7 @@ export async function createNewPost( { content, excerpt, showWelcomeGuide = false, + legacyCanvas = false, } = {} ) { const query = addQueryArgs( '', { post_type: postType, @@ -29,7 +31,15 @@ export async function createNewPost( { } ).slice( 1 ); await this.visitAdminPage( 'post-new.php', query ); - await this.page.waitForSelector( '.edit-post-layout' ); + + const canvasReadyLocator = legacyCanvas + ? this.page.locator( '.edit-post-layout' ) + : this.page + .frameLocator( '[name=editor-canvas]' ) + .locator( 'body > *' ) + .first(); + + await canvasReadyLocator.waitFor(); await this.page.evaluate( ( welcomeGuide ) => { window.wp.data diff --git a/packages/e2e-test-utils-playwright/src/admin/visit-admin-page.ts b/packages/e2e-test-utils-playwright/src/admin/visit-admin-page.ts index 60aec50837694f..1ffb20c6cc3f86 100644 --- a/packages/e2e-test-utils-playwright/src/admin/visit-admin-page.ts +++ b/packages/e2e-test-utils-playwright/src/admin/visit-admin-page.ts @@ -18,7 +18,7 @@ import type { Admin } from './'; export async function visitAdminPage( this: Admin, adminPath: string, - query: string + query?: string ) { await this.page.goto( join( 'wp-admin', adminPath ) + ( query ? `?${ query }` : '' ) diff --git a/packages/e2e-test-utils-playwright/src/test.ts b/packages/e2e-test-utils-playwright/src/test.ts index 894abf93dcd02e..eec8e4e279c0ff 100644 --- a/packages/e2e-test-utils-playwright/src/test.ts +++ b/packages/e2e-test-utils-playwright/src/test.ts @@ -136,18 +136,6 @@ const test = base.extend< storageStatePath: STORAGE_STATE_PATH, } ); - await Promise.all( [ - requestUtils.activateTheme( 'twentytwentyone' ), - // Disable this test plugin as it's conflicting with some of the tests. - // We already have reduced motion enabled and Playwright will wait for most of the animations anyway. - requestUtils.deactivatePlugin( - 'gutenberg-test-plugin-disables-the-css-animations' - ), - requestUtils.deleteAllPosts(), - requestUtils.deleteAllBlocks(), - requestUtils.resetPreferences(), - ] ); - await use( requestUtils ); }, { scope: 'worker', auto: true }, diff --git a/packages/e2e-test-utils/CHANGELOG.md b/packages/e2e-test-utils/CHANGELOG.md index 643b335f883241..e5a7bd38190174 100644 --- a/packages/e2e-test-utils/CHANGELOG.md +++ b/packages/e2e-test-utils/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 10.8.0 (2023-07-05) + ## 10.7.0 (2023-06-23) ## 10.6.0 (2023-06-07) diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index 69fa428544418b..1929472c8160e6 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": "10.7.0", + "version": "10.8.0", "description": "End-To-End (E2E) test utils for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-test-utils/src/create-reusable-block.js b/packages/e2e-test-utils/src/create-reusable-block.js index 7193db49a83efb..860b5ac5660eb4 100644 --- a/packages/e2e-test-utils/src/create-reusable-block.js +++ b/packages/e2e-test-utils/src/create-reusable-block.js @@ -15,8 +15,6 @@ import { canvas } from './canvas'; export const createReusableBlock = async ( content, title ) => { const reusableBlockNameInputSelector = '.reusable-blocks-menu-items__convert-modal .components-text-control__input'; - const syncToggleSelector = - '.reusable-blocks-menu-items__convert-modal .components-form-toggle__input'; const syncToggleSelectorChecked = '.reusable-blocks-menu-items__convert-modal .components-form-toggle.is-checked'; // Insert a paragraph block @@ -24,21 +22,19 @@ export const createReusableBlock = async ( content, title ) => { await page.keyboard.type( content ); await clickBlockToolbarButton( 'Options' ); - await clickMenuItem( 'Create pattern' ); + await clickMenuItem( 'Create pattern/reusable block' ); const nameInput = await page.waitForSelector( reusableBlockNameInputSelector ); await nameInput.click(); await page.keyboard.type( title ); - const syncToggle = await page.waitForSelector( syncToggleSelector ); - syncToggle.click(); await page.waitForSelector( syncToggleSelectorChecked ); await page.keyboard.press( 'Enter' ); // Wait for creation to finish await page.waitForXPath( - '//*[contains(@class, "components-snackbar")]/*[text()="Synced Pattern created."]' + '//*[contains(@class, "components-snackbar")]/*[contains(text(),"Pattern created:")]' ); // Check that we have a reusable block on the page diff --git a/packages/e2e-test-utils/src/install-plugin.js b/packages/e2e-test-utils/src/install-plugin.js index a2f8e493294196..5edfbb54f6642a 100644 --- a/packages/e2e-test-utils/src/install-plugin.js +++ b/packages/e2e-test-utils/src/install-plugin.js @@ -20,6 +20,8 @@ export async function installPlugin( slug, searchTerm ) { '&tab=search&type=term' ); await page.click( `.install-now[data-slug="${ slug }"]` ); - await page.waitForSelector( `.activate-now[data-slug="${ slug }"]` ); + await page.waitForSelector( `.activate-now[data-slug="${ slug }"]`, { + timeout: 60000, + } ); await switchUserToTest(); } diff --git a/packages/e2e-test-utils/src/install-theme.js b/packages/e2e-test-utils/src/install-theme.js index d030165c7cb97c..7d001d395bda7f 100644 --- a/packages/e2e-test-utils/src/install-theme.js +++ b/packages/e2e-test-utils/src/install-theme.js @@ -37,6 +37,8 @@ export async function installTheme( slug, { searchTerm } = {} ) { 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 page.waitForSelector( `.theme[data-slug="${ slug }"] .activate`, { + timeout: 60000, + } ); await switchUserToTest(); } diff --git a/packages/e2e-test-utils/src/login-user.js b/packages/e2e-test-utils/src/login-user.js index aeecfd9d5cc612..976153bce8f616 100644 --- a/packages/e2e-test-utils/src/login-user.js +++ b/packages/e2e-test-utils/src/login-user.js @@ -17,7 +17,9 @@ export async function loginUser( password = WP_PASSWORD ) { if ( ! isCurrentURL( 'wp-login.php' ) ) { + const waitForLoginPageNavigation = page.waitForNavigation(); await page.goto( createURL( 'wp-login.php' ) ); + await waitForLoginPageNavigation; } await page.focus( '#user_login' ); @@ -27,8 +29,6 @@ export async function loginUser( await pressKeyWithModifier( 'primary', 'a' ); await page.type( '#user_pass', password ); - await Promise.all( [ - page.waitForNavigation(), - page.click( '#wp-submit' ), - ] ); + const waitForLoginNavigation = page.waitForNavigation(); + await Promise.all( [ waitForLoginNavigation, page.click( '#wp-submit' ) ] ); } diff --git a/packages/e2e-tests/CHANGELOG.md b/packages/e2e-tests/CHANGELOG.md index b8a2cfdbb8f555..c591ca9caa0bd7 100644 --- a/packages/e2e-tests/CHANGELOG.md +++ b/packages/e2e-tests/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 7.8.0 (2023-07-05) + ## 7.7.0 (2023-06-23) ## 7.6.0 (2023-06-07) diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index de9108f1b59a97..aaef06e196b7c0 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-tests", - "version": "7.7.0", + "version": "7.8.0", "description": "End-To-End (E2E) tests for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-tests/plugins/block-api.php b/packages/e2e-tests/plugins/block-api.php index e0609f7adc0ffc..265ada8ab8ed94 100644 --- a/packages/e2e-tests/plugins/block-api.php +++ b/packages/e2e-tests/plugins/block-api.php @@ -16,6 +16,8 @@ function enqueue_block_api_plugin_script() { plugins_url( 'block-api/index.js', __FILE__ ), array( 'wp-blocks', + 'wp-block-editor', + 'wp-element', 'wp-hooks', ), filemtime( plugin_dir_path( __FILE__ ) . 'block-api/index.js' ), diff --git a/packages/e2e-tests/plugins/block-api/index.js b/packages/e2e-tests/plugins/block-api/index.js index 99a6ab3cdcc838..cb171ab1b442bd 100644 --- a/packages/e2e-tests/plugins/block-api/index.js +++ b/packages/e2e-tests/plugins/block-api/index.js @@ -1,13 +1,16 @@ ( function () { const { registerBlockType } = wp.blocks; + const { useBlockProps } = wp.blockEditor; + const { createElement: el } = wp.element; const { addFilter } = wp.hooks; registerBlockType( 'e2e-tests/hello-world', { + apiVersion: 3, title: 'Hello World', description: 'Hello World test block.', category: 'widgets', - edit() { - return 'Hello Editor!'; + edit: function Edit() { + return el( 'p', useBlockProps(), 'Hello Editor!' ); }, save() { return 'Hello Frontend!'; diff --git a/packages/e2e-tests/plugins/block-context.php b/packages/e2e-tests/plugins/block-context.php index dc92132152e1f0..d245efce2a6156 100644 --- a/packages/e2e-tests/plugins/block-context.php +++ b/packages/e2e-tests/plugins/block-context.php @@ -8,10 +8,10 @@ */ /** - * Enqueues a custom script for the plugin. + * Registers plugin test context blocks. */ -function gutenberg_test_enqueue_block_context_script() { - wp_enqueue_script( +function gutenberg_test_register_context_blocks() { + wp_register_script( 'gutenberg-test-block-context', plugins_url( 'block-context/index.js', __FILE__ ), array( @@ -22,37 +22,32 @@ function gutenberg_test_enqueue_block_context_script() { filemtime( plugin_dir_path( __FILE__ ) . 'block-context/index.js' ), true ); -} -add_action( 'init', 'gutenberg_test_enqueue_block_context_script' ); -/** - * Registers plugin test context blocks. - */ -function gutenberg_test_register_context_blocks() { register_block_type( 'gutenberg/test-context-provider', array( - 'attributes' => array( + 'attributes' => array( 'recordId' => array( 'type' => 'number', 'default' => 0, ), ), - 'provides_context' => array( + 'provides_context' => array( 'gutenberg/recordId' => 'recordId', ), + 'editor_script_handles' => array( 'gutenberg-test-block-context' ), ) ); register_block_type( 'gutenberg/test-context-consumer', array( - 'uses_context' => array( + 'uses_context' => array( 'gutenberg/recordId', 'postId', 'postType', ), - 'render_callback' => static function( $attributes, $content, $block ) { + 'render_callback' => static function( $attributes, $content, $block ) { $ordered_context = array( $block->context['gutenberg/recordId'], $block->context['postId'], @@ -61,6 +56,7 @@ function gutenberg_test_register_context_blocks() { return implode( ',', $ordered_context ); }, + 'editor_script_handles' => array( 'gutenberg-test-block-context' ), ) ); } diff --git a/packages/e2e-tests/plugins/deprecated-node-matcher.php b/packages/e2e-tests/plugins/deprecated-node-matcher.php index 7e0a6e3d2c5578..0356718b02e974 100644 --- a/packages/e2e-tests/plugins/deprecated-node-matcher.php +++ b/packages/e2e-tests/plugins/deprecated-node-matcher.php @@ -15,7 +15,6 @@ function enqueue_deprecated_node_matcher_plugin_script() { 'gutenberg-test-deprecated-node-matcher', plugins_url( 'deprecated-node-matcher/index.js', __FILE__ ), array( - 'lodash', 'wp-blocks', 'wp-element', 'wp-block-editor', diff --git a/packages/e2e-tests/plugins/deprecated-node-matcher/index.js b/packages/e2e-tests/plugins/deprecated-node-matcher/index.js index a7edacf6739b86..94b59f6190166e 100644 --- a/packages/e2e-tests/plugins/deprecated-node-matcher/index.js +++ b/packages/e2e-tests/plugins/deprecated-node-matcher/index.js @@ -1,9 +1,10 @@ ( function () { const registerBlockType = wp.blocks.registerBlockType; - const RichText = wp.blockEditor.RichText; + const { useBlockProps, RichText } = wp.blockEditor; const el = wp.element.createElement; registerBlockType( 'core/deprecated-children-matcher', { + apiVersion: 3, title: 'Deprecated Children Matcher', attributes: { value: { @@ -13,40 +14,35 @@ }, }, category: 'text', - edit( { attributes, setAttributes } ) { + edit: function EditChildrenMatcher( { attributes, setAttributes } ) { return el( RichText, { tagName: 'p', value: attributes.value, onChange( nextValue ) { setAttributes( { value: nextValue } ); }, + ...useBlockProps(), } ); }, save( { attributes } ) { return el( RichText.Content, { tagName: 'p', value: attributes.value, + ...useBlockProps.save(), } ); }, } ); function toRichTextValue( value ) { - // eslint-disable-next-line no-undef - return _.map( value, function ( subValue ) { - return subValue.children; - } ); + return value?.map( ( { children } ) => children ) ?? []; } function fromRichTextValue( value ) { - // eslint-disable-next-line no-undef - return _.map( value, function ( subValue ) { - return { - children: subValue, - }; - } ); + return value.map( ( subValue ) => ( { children: subValue } ) ); } registerBlockType( 'core/deprecated-node-matcher', { + apiVersion: 3, title: 'Deprecated Node Matcher', attributes: { value: { @@ -61,10 +57,10 @@ }, }, category: 'text', - edit( { attributes, setAttributes } ) { + edit: function EditNodeMatcher( { attributes, setAttributes } ) { return el( 'blockquote', - {}, + useBlockProps(), el( RichText, { multiline: 'p', value: toRichTextValue( attributes.value ), @@ -74,12 +70,12 @@ } ); }, } ) - ); + ) }, save( { attributes } ) { return el( 'blockquote', - {}, + useBlockProps.save(), el( RichText.Content, { value: toRichTextValue( attributes.value ), } ) diff --git a/packages/e2e-tests/plugins/iframed-enqueue-block-assets.php b/packages/e2e-tests/plugins/iframed-enqueue-block-assets.php index ad98354dd45dc7..3f24a6e25cfcb5 100644 --- a/packages/e2e-tests/plugins/iframed-enqueue-block-assets.php +++ b/packages/e2e-tests/plugins/iframed-enqueue-block-assets.php @@ -17,5 +17,18 @@ static function() { filemtime( plugin_dir_path( __FILE__ ) . 'iframed-enqueue-block-assets/style.css' ) ); wp_add_inline_style( 'iframed-enqueue-block-assets', 'body{padding:20px!important}' ); + wp_enqueue_script( + 'iframed-enqueue-block-assets-script', + plugin_dir_url( __FILE__ ) . 'iframed-enqueue-block-assets/script.js', + array(), + filemtime( plugin_dir_path( __FILE__ ) . 'iframed-enqueue-block-assets/script.js' ) + ); + wp_localize_script( + 'iframed-enqueue-block-assets-script', + 'iframedEnqueueBlockAssetsL10n', + array( + 'test' => 'Iframed Enqueue Block Assets!', + ) + ); } ); diff --git a/packages/e2e-tests/plugins/iframed-enqueue-block-assets/script.js b/packages/e2e-tests/plugins/iframed-enqueue-block-assets/script.js new file mode 100644 index 00000000000000..f0eddd65c70ebe --- /dev/null +++ b/packages/e2e-tests/plugins/iframed-enqueue-block-assets/script.js @@ -0,0 +1,3 @@ +window.addEventListener( 'load', () => { + document.body.dataset.iframedEnqueueBlockAssetsL10n = window.iframedEnqueueBlockAssetsL10n.test; +} ); diff --git a/packages/e2e-tests/plugins/inner-blocks-allowed-blocks/index.js b/packages/e2e-tests/plugins/inner-blocks-allowed-blocks/index.js index e6c35785d1a5d1..c32307b2d49ff1 100644 --- a/packages/e2e-tests/plugins/inner-blocks-allowed-blocks/index.js +++ b/packages/e2e-tests/plugins/inner-blocks-allowed-blocks/index.js @@ -12,6 +12,7 @@ const allowedBlocksWhenMultipleChildren = [ 'core/gallery', 'core/video' ]; registerBlockType( 'test/allowed-blocks-dynamic', { + apiVersion: 3, title: 'Allowed Blocks Dynamic', icon: 'carrot', category: 'text', diff --git a/packages/e2e-tests/plugins/inner-blocks-templates/index.js b/packages/e2e-tests/plugins/inner-blocks-templates/index.js index 959f4a3eeb5a0b..5a49a0fa0aa057 100644 --- a/packages/e2e-tests/plugins/inner-blocks-templates/index.js +++ b/packages/e2e-tests/plugins/inner-blocks-templates/index.js @@ -2,7 +2,7 @@ const registerBlockType = wp.blocks.registerBlockType; const createBlock = wp.blocks.createBlock; const el = wp.element.createElement; - const InnerBlocks = wp.blockEditor.InnerBlocks; + const { InnerBlocks, useBlockProps } = wp.blockEditor; const useState = window.wp.element.useState; const TEMPLATE = [ @@ -47,35 +47,38 @@ }; registerBlockType( 'test/test-inner-blocks-no-locking', { + apiVersion: 3, title: 'Test Inner Blocks no locking', icon: 'cart', category: 'text', - edit() { - return el( InnerBlocks, { + edit: function InnerBlocksNoLockingEdit() { + return el( 'div', useBlockProps(), el( InnerBlocks, { template: TEMPLATE, - } ); + } ) ); }, save, } ); registerBlockType( 'test/test-inner-blocks-locking-all', { + apiVersion: 3, title: 'Test InnerBlocks locking all', icon: 'cart', category: 'text', - edit() { - return el( InnerBlocks, { + edit: function InnerBlocksBlocksLockingAllEdit() { + return el( 'div', useBlockProps(), el( InnerBlocks, { template: TEMPLATE, templateLock: 'all', - } ); + } ) ); }, save, } ); registerBlockType( 'test/test-inner-blocks-update-locked-template', { + apiVersion: 3, title: 'Test Inner Blocks update locked template', icon: 'cart', category: 'text', @@ -87,7 +90,7 @@ }, }, - edit( props ) { + edit: function InnerBlocksUpdateLockedTemplateEdit( props ) { const hasUpdatedTemplated = props.attributes.hasUpdatedTemplate; return el( 'div', null, [ el( @@ -99,12 +102,12 @@ }, 'Update template' ), - el( InnerBlocks, { + el( 'div', useBlockProps(), el( InnerBlocks, { template: hasUpdatedTemplated ? TEMPLATE_TWO_PARAGRAPHS : TEMPLATE, templateLock: 'all', - } ), + } ) ), ] ); }, @@ -112,21 +115,23 @@ } ); registerBlockType( 'test/test-inner-blocks-paragraph-placeholder', { + apiVersion: 3, title: 'Test Inner Blocks Paragraph Placeholder', icon: 'cart', category: 'text', - edit() { - return el( InnerBlocks, { + edit: function InnerBlocksParagraphPlaceholderEdit() { + return el( 'div', useBlockProps(), el( InnerBlocks, { template: TEMPLATE_PARAGRAPH_PLACEHOLDER, templateInsertUpdatesSelection: true, - } ); + } ) ); }, save, } ); registerBlockType( 'test/test-inner-blocks-transformer-target', { + apiVersion: 3, title: 'Test Inner Blocks transformer target', icon: 'cart', category: 'text', @@ -165,36 +170,34 @@ ], }, - edit() { - return el( InnerBlocks, { + edit: function InnerBlocksTransformerTargetEdit() { + return el( 'div', useBlockProps(), el( InnerBlocks, { template: TEMPLATE, - } ); + } ) ); }, save, } ); - - function InnerBlocksAsyncTemplateEdit() { - const [ template, setTemplate ] = useState( [] ); - - setInterval( () => { - setTemplate( TEMPLATE_TWO_PARAGRAPHS ); - }, 1000 ); - - return el( InnerBlocks, { - template, - } ); - } - registerBlockType( 'test/test-inner-blocks-async-template', { + apiVersion: 3, title: 'Test Inner Blocks Async Template', icon: 'cart', category: 'text', - edit: InnerBlocksAsyncTemplateEdit, + edit: function InnerBlocksAsyncTemplateEdit() { + const [ template, setTemplate ] = useState( [] ); + + setInterval( () => { + setTemplate( TEMPLATE_TWO_PARAGRAPHS ); + }, 1000 ); + + return el('div', useBlockProps(), el( InnerBlocks, { + template, + } ) ); + }, // Purposely do not save inner blocks so that it's possible to test template resolution. save() {}, diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-style/block.json b/packages/e2e-tests/plugins/interactive-blocks/directive-style/block.json new file mode 100644 index 00000000000000..6cbfa57b0784f1 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-style/block.json @@ -0,0 +1,14 @@ +{ + "apiVersion": 2, + "name": "test/directive-style", + "title": "E2E Interactivity tests - directive style", + "category": "text", + "icon": "heart", + "description": "", + "supports": { + "interactivity": true + }, + "textdomain": "e2e-interactivity", + "viewScript": "directive-style-view", + "render": "file:./render.php" +} diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-style/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-style/render.php new file mode 100644 index 00000000000000..4272333c3ec277 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-style/render.php @@ -0,0 +1,93 @@ + + +
      + + + + +
      Don't change style if callback returns same value on hydration
      + +
      Remove style if callback returns falsy value on hydration
      + +
      Change style if callback returns a new value on hydration
      + +
      Handles multiple styles and callbacks on hydration
      + +
      Can add style when style attribute is missing on hydration
      + +
      Can toggle style
      + +
      Can remove style
      + +
      Can toggle style in the middle
      + +
      Handles styles names with hyphens
      + +
      +
      + +
      +
      diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-style/view.js b/packages/e2e-tests/plugins/interactive-blocks/directive-style/view.js new file mode 100644 index 00000000000000..04fbf1aab11403 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-style/view.js @@ -0,0 +1,22 @@ +( ( { wp } ) => { + const { store } = wp.interactivity; + + store( { + state: { + falseValue: false, + color: "red", + border: "2px solid yellow" + }, + actions: { + toggleColor: ( { state } ) => { + state.color = state.color === "red" ? "blue" : "red"; + }, + switchColorToFalse: ({ state }) => { + state.color = false; + }, + toggleContext: ( { context } ) => { + context.color = context.color === "red" ? "blue" : "red"; + }, + }, + } ); +} )( window ); diff --git a/packages/e2e-tests/specs/editor/plugins/container-blocks.test.js b/packages/e2e-tests/specs/editor/plugins/container-blocks.test.js index 4c159571da9fd8..88c1a7fc8271ae 100644 --- a/packages/e2e-tests/specs/editor/plugins/container-blocks.test.js +++ b/packages/e2e-tests/specs/editor/plugins/container-blocks.test.js @@ -9,6 +9,7 @@ import { insertBlock, switchEditorModeTo, pressKeyWithModifier, + canvas, } from '@wordpress/e2e-test-utils'; describe( 'InnerBlocks Template Sync', () => { @@ -75,7 +76,7 @@ describe( 'InnerBlocks Template Sync', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); // Trigger a template update and assert that a second block is now present. - const [ button ] = await page.$x( + const [ button ] = await canvas().$x( `//button[contains(text(), 'Update template')]` ); await button.click(); diff --git a/packages/e2e-tests/specs/editor/plugins/iframed-equeue-block-assets.test.js b/packages/e2e-tests/specs/editor/plugins/iframed-equeue-block-assets.test.js index c1bd26fe1c7610..c29af593abb124 100644 --- a/packages/e2e-tests/specs/editor/plugins/iframed-equeue-block-assets.test.js +++ b/packages/e2e-tests/specs/editor/plugins/iframed-equeue-block-assets.test.js @@ -32,6 +32,7 @@ describe( 'iframed inline styles', () => { } ); it( 'should load styles added through enqueue_block_assets', async () => { + await page.waitForSelector( 'iframe[name="editor-canvas"]' ); // Check stylesheet. expect( await getComputedStyle( canvas(), 'body', 'background-color' ) @@ -40,5 +41,11 @@ describe( 'iframed inline styles', () => { expect( await getComputedStyle( canvas(), 'body', 'padding' ) ).toBe( '20px' ); + + expect( + await canvas().evaluate( () => ( { ...document.body.dataset } ) ) + ).toEqual( { + iframedEnqueueBlockAssetsL10n: 'Iframed Enqueue Block Assets!', + } ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/adding-inline-tokens.test.js b/packages/e2e-tests/specs/editor/various/adding-inline-tokens.test.js deleted file mode 100644 index 7c04bae1f95ddb..00000000000000 --- a/packages/e2e-tests/specs/editor/various/adding-inline-tokens.test.js +++ /dev/null @@ -1,80 +0,0 @@ -/** - * External dependencies - */ -import path from 'path'; -import fs from 'fs'; -import os from 'os'; -import { v4 as uuid } from 'uuid'; - -/** - * WordPress dependencies - */ -import { - clickBlockAppender, - getEditedPostContent, - createNewPost, - clickBlockToolbarButton, - clickButton, - pressKeyWithModifier, -} from '@wordpress/e2e-test-utils'; - -describe( 'adding inline tokens', () => { - beforeEach( async () => { - await createNewPost(); - } ); - - it( 'should insert inline image', async () => { - // Create a paragraph. - await clickBlockAppender(); - await page.keyboard.type( 'a ' ); - - await clickBlockToolbarButton( 'More' ); - await clickButton( 'Inline image' ); - - // Wait for media modal to appear and upload image. - // Wait for media modal to appear and upload image. - const inputElement = await page.waitForSelector( - '.media-modal .moxie-shim input[type=file]' - ); - const testImagePath = path.join( - __dirname, - '..', - '..', - '..', - 'assets', - '10x10_e2e_test_image_z9T8jK.png' - ); - const filename = uuid(); - const tmpFileName = path.join( os.tmpdir(), filename + '.png' ); - fs.copyFileSync( testImagePath, tmpFileName ); - await inputElement.uploadFile( tmpFileName ); - - // Wait for upload. - await page.waitForSelector( - `.media-modal li[aria-label="${ filename }"]` - ); - - // Insert the uploaded image. - await page.click( '.media-modal button.media-button-select' ); - - // Check the content. - const regex = new RegExp( - `\\s*

      a <\\/p>\\s*` - ); - expect( await getEditedPostContent() ).toMatch( regex ); - - await pressKeyWithModifier( 'shift', 'ArrowLeft' ); - await page.waitForSelector( - '.block-editor-format-toolbar__image-popover' - ); - await page.keyboard.press( 'Tab' ); - await page.keyboard.type( '20' ); - await page.keyboard.press( 'Enter' ); - - // Check the content. - const regex2 = new RegExp( - `\\s*

      a <\\/p>\\s*` - ); - expect( await getEditedPostContent() ).toMatch( regex2 ); - } ); -} ); diff --git a/packages/e2e-tests/specs/editor/various/block-editor-keyboard-shortcuts.test.js b/packages/e2e-tests/specs/editor/various/block-editor-keyboard-shortcuts.test.js index 24e8e3104aaaa2..3be73830a42991 100644 --- a/packages/e2e-tests/specs/editor/various/block-editor-keyboard-shortcuts.test.js +++ b/packages/e2e-tests/specs/editor/various/block-editor-keyboard-shortcuts.test.js @@ -90,7 +90,7 @@ describe( 'block editor keyboard shortcuts', () => { } ); it( 'should prevent deleting multiple selected blocks from inputs', async () => { await clickBlockToolbarButton( 'Options' ); - await clickMenuItem( 'Create pattern' ); + await clickMenuItem( 'Create pattern/reusable block' ); const reusableBlockNameInputSelector = '.reusable-blocks-menu-items__convert-modal .components-text-control__input'; const nameInput = await page.waitForSelector( 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 b6461ef11bc5b4..90ef0950e535bb 100644 --- a/packages/e2e-tests/specs/editor/various/publish-button.test.js +++ b/packages/e2e-tests/specs/editor/various/publish-button.test.js @@ -43,19 +43,4 @@ describe( 'PostPublishButton', () => { await page.$( '.editor-post-publish-button[aria-disabled="true"]' ) ).not.toBeNull(); } ); - - it( 'should be disabled when metabox is being saved', async () => { - await canvas().type( '.editor-post-title__input', 'E2E Test Post' ); // Make it saveable. - expect( - await page.$( '.editor-post-publish-button[aria-disabled="true"]' ) - ).toBeNull(); - - 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 1ffd4e24143362..1fc9217b4a77c0 100644 --- a/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js @@ -23,8 +23,6 @@ const reusableBlockNameInputSelector = '.reusable-blocks-menu-items__convert-modal .components-text-control__input'; const reusableBlockInspectorNameInputSelector = '.block-editor-block-inspector .components-text-control__input'; -const syncToggleSelector = - '.reusable-blocks-menu-items__convert-modal .components-form-toggle__input'; const syncToggleSelectorChecked = '.reusable-blocks-menu-items__convert-modal .components-form-toggle.is-checked'; @@ -197,7 +195,7 @@ describe( 'Reusable blocks', () => { // Convert block to a reusable block. await clickBlockToolbarButton( 'Options' ); - await clickMenuItem( 'Create pattern' ); + await clickMenuItem( 'Create pattern/reusable block' ); // Set title. const nameInput = await page.waitForSelector( @@ -205,14 +203,12 @@ describe( 'Reusable blocks', () => { ); await nameInput.click(); await page.keyboard.type( 'Multi-selection reusable block' ); - const syncToggle = await page.waitForSelector( syncToggleSelector ); - syncToggle.click(); await page.waitForSelector( syncToggleSelectorChecked ); await page.keyboard.press( 'Enter' ); // Wait for creation to finish. await page.waitForXPath( - '//*[contains(@class, "components-snackbar")]/*[text()="Synced Pattern created."]' + '//*[contains(@class, "components-snackbar")]/*[contains(text(),"Pattern created:")]' ); await clearAllBlocks(); @@ -352,7 +348,7 @@ describe( 'Reusable blocks', () => { expect( reusableBlockWithParagraph ).toBeTruthy(); // Convert back to regular blocks. - await clickBlockToolbarButton( 'Select Pattern' ); + await clickBlockToolbarButton( 'Select Edited block' ); await clickBlockToolbarButton( 'Detach pattern' ); await page.waitForXPath( selector, { hidden: true, @@ -383,14 +379,12 @@ describe( 'Reusable blocks', () => { // Convert to reusable. await clickBlockToolbarButton( 'Options' ); - await clickMenuItem( 'Create pattern' ); + await clickMenuItem( 'Create pattern/reusable block' ); const nameInput = await page.waitForSelector( reusableBlockNameInputSelector ); await nameInput.click(); await page.keyboard.type( 'Block with styles' ); - const syncToggle = await page.waitForSelector( syncToggleSelector ); - syncToggle.click(); await page.waitForSelector( syncToggleSelectorChecked ); await page.keyboard.press( 'Enter' ); const reusableBlock = await canvas().waitForSelector( diff --git a/packages/e2e-tests/specs/site-editor/multi-entity-saving.test.js b/packages/e2e-tests/specs/site-editor/multi-entity-saving.test.js index d54f9c78dd8b89..c2bd3cb6d75076 100644 --- a/packages/e2e-tests/specs/site-editor/multi-entity-saving.test.js +++ b/packages/e2e-tests/specs/site-editor/multi-entity-saving.test.js @@ -12,8 +12,6 @@ import { activateTheme, clickButton, createReusableBlock, - visitSiteEditor, - enterEditMode, deleteAllTemplates, canvas, } from '@wordpress/e2e-test-utils'; @@ -239,104 +237,4 @@ describe( 'Multi-entity save flow', () => { expect( checkboxInputs ).toHaveLength( 1 ); } ); } ); - - describe( 'Site Editor', () => { - // Selectors - Site editor specific. - const saveSiteSelector = '.edit-site-save-button__button'; - const activeSaveSiteSelector = `${ saveSiteSelector }[aria-disabled=false]`; - const disabledSaveSiteSelector = `${ saveSiteSelector }[aria-disabled=true]`; - const saveA11ySelector = '.edit-site-editor__toggle-save-panel-button'; - - const saveAllChanges = async () => { - // Clicking button should open panel with boxes checked. - await page.click( activeSaveSiteSelector ); - await page.waitForSelector( savePanelSelector ); - await assertAllBoxesChecked(); - - // Save a11y button should not be present with save panel open. - await assertExistence( saveA11ySelector, false ); - - // Saving should result in items being saved. - await page.click( entitiesSaveSelector ); - }; - - it( 'Save flow should work as expected', async () => { - // Navigate to site editor. - await visitSiteEditor( { - postId: 'emptytheme//index', - postType: 'wp_template', - } ); - - await enterEditMode(); - - // Select the header template part via list view. - await page.click( '.edit-site-header-edit-mode__list-view-toggle' ); - const headerTemplatePartListViewButton = await page.waitForXPath( - '//a[contains(@class, "block-editor-list-view-block-select-button")][contains(., "header")]' - ); - headerTemplatePartListViewButton.click(); - await page.click( 'button[aria-label="Close"]' ); - - // Insert something to dirty the editor. - await insertBlock( 'Paragraph' ); - - const enabledButton = await page.waitForSelector( - activeSaveSiteSelector - ); - - // Should be enabled after edits. - expect( enabledButton ).not.toBeNull(); - - // Save a11y button should be present. - await assertExistence( saveA11ySelector, true ); - - // Save all changes. - await saveAllChanges(); - - const disabledButton = await page.waitForSelector( - disabledSaveSiteSelector - ); - expect( disabledButton ).not.toBeNull(); - } ); - - it( 'Save flow should allow re-saving after changing the same block attribute', async () => { - // Navigate to site editor. - await visitSiteEditor( { - postId: 'emptytheme//index', - postType: 'wp_template', - } ); - - await enterEditMode(); - - // Insert a paragraph at the bottom. - await insertBlock( 'Paragraph' ); - - // Open the block settings. - await page.click( 'button[aria-label="Settings"]' ); - - // Wait for the font size picker controls. - await page.waitForSelector( - '.components-font-size-picker__controls' - ); - - // Change the font size. - await page.click( - '.components-font-size-picker__controls button[aria-label="Small"]' - ); - - // Save all changes. - await saveAllChanges(); - - // Change the font size. - await page.click( - '.components-font-size-picker__controls button[aria-label="Medium"]' - ); - - // Assert that the save button has been re-enabled. - const saveButton = await page.waitForSelector( - activeSaveSiteSelector - ); - expect( saveButton ).not.toBeNull(); - } ); - } ); } ); diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index bdeb9086a2dd71..5ddb12e69276dd 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 7.14.0 (2023-07-05) + ## 7.13.0 (2023-06-23) ## 7.12.0 (2023-06-07) diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index d4f9cb16bd0502..26310deccf4481 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "7.13.0", + "version": "7.14.0", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-post/src/components/block-manager/style.scss b/packages/edit-post/src/components/block-manager/style.scss index 2568856be41ab5..c62e5fea93202d 100644 --- a/packages/edit-post/src/components/block-manager/style.scss +++ b/packages/edit-post/src/components/block-manager/style.scss @@ -36,7 +36,7 @@ .edit-post-block-manager__category-title { position: sticky; - top: 0; + top: - $grid-unit-05; // Offsets the top padding on the modal content container padding: $grid-unit-20 0; background-color: $white; z-index: z-index(".edit-post-block-manager__category-title"); diff --git a/packages/edit-post/src/components/device-preview/index.js b/packages/edit-post/src/components/device-preview/index.js index 5c061c92fde8e0..ab38523969ace1 100644 --- a/packages/edit-post/src/components/device-preview/index.js +++ b/packages/edit-post/src/components/device-preview/index.js @@ -15,26 +15,22 @@ import { store as coreStore } from '@wordpress/core-data'; import { store as editPostStore } from '../../store'; export default function DevicePreview() { - const { - hasActiveMetaboxes, - isPostSaveable, - isSaving, - isViewable, - deviceType, - } = useSelect( ( select ) => { - const { getEditedPostAttribute } = select( editorStore ); - const { getPostType } = select( coreStore ); - const postType = getPostType( getEditedPostAttribute( 'type' ) ); + const { hasActiveMetaboxes, isPostSaveable, isViewable, deviceType } = + useSelect( ( select ) => { + const { getEditedPostAttribute } = select( editorStore ); + const { getPostType } = select( coreStore ); + const postType = getPostType( getEditedPostAttribute( 'type' ) ); - return { - hasActiveMetaboxes: select( editPostStore ).hasMetaBoxes(), - isSaving: select( editPostStore ).isSavingMetaBoxes(), - isPostSaveable: select( editorStore ).isEditedPostSaveable(), - isViewable: postType?.viewable ?? false, - deviceType: - select( editPostStore ).__experimentalGetPreviewDeviceType(), - }; - }, [] ); + return { + hasActiveMetaboxes: select( editPostStore ).hasMetaBoxes(), + isPostSaveable: select( editorStore ).isEditedPostSaveable(), + isViewable: postType?.viewable ?? false, + deviceType: + select( + editPostStore + ).__experimentalGetPreviewDeviceType(), + }; + }, [] ); const { __experimentalSetPreviewDeviceType: setPreviewDeviceType } = useDispatch( editPostStore ); @@ -46,26 +42,26 @@ export default function DevicePreview() { setDeviceType={ setPreviewDeviceType } label={ __( 'Preview' ) } > - { isViewable && ( - -

      - - { __( 'Preview in new tab' ) } - - - } - /> -
      - - ) } + { ( { onClose } ) => + isViewable && ( + +
      + + { __( 'Preview in new tab' ) } + + + } + onPreview={ onClose } + /> +
      +
      + ) + } ); } 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 391e5473999bb7..bcb81804272916 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -19,6 +19,7 @@ import { Button, ToolbarItem } from '@wordpress/components'; import { listView, plus } from '@wordpress/icons'; import { useRef, useCallback } from '@wordpress/element'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; +import { store as preferencesStore } from '@wordpress/preferences'; /** * Internal dependencies @@ -43,6 +44,7 @@ function HeaderToolbar() { showIconLabels, isListViewOpen, listViewShortcut, + hasFixedToolbar, } = useSelect( ( select ) => { const { hasInserterItems, getBlockRootClientId, getBlockSelectionEnd } = select( blockEditorStore ); @@ -50,6 +52,7 @@ function HeaderToolbar() { const { getEditorMode, isFeatureActive, isListViewOpened } = select( editPostStore ); const { getShortcutRepresentation } = select( keyboardShortcutsStore ); + const { get: getPreference } = select( preferencesStore ); return { // This setting (richEditingEnabled) should not live in the block editor's setting. @@ -66,6 +69,7 @@ function HeaderToolbar() { listViewShortcut: getShortcutRepresentation( 'core/edit-post/toggle-list-view' ), + hasFixedToolbar: getPreference( 'core/edit-post', 'fixedToolbar' ), }; }, [] ); @@ -147,7 +151,7 @@ function HeaderToolbar() { /> { ( isWideViewport || ! showIconLabels ) && ( <> - { isLargeViewport && ( + { isLargeViewport && ! hasFixedToolbar && ( { + const onUndoSubscription = subscribeOnUndoPressed( undo ); + const onRedoSubscription = subscribeOnRedoPressed( redo ); + + return () => { + onUndoSubscription?.remove(); + onRedoSubscription?.remove(); + }; + }, [ undo, redo ] ); + + useEffect( () => { + toggleUndoButton( ! hasUndo ); + }, [ hasUndo ] ); + + useEffect( () => { + toggleRedoButton( ! hasRedo ); + }, [ hasRedo ] ); const scrollViewRef = useRef( null ); const scrollToStart = () => { // scrollview doesn't seem to automatically adjust to RTL on Android so, scroll to end when Android - const isAndroid = Platform.OS === 'android'; - if ( isAndroid && isRTL ) { + if ( Platform.isAndroid && isRTL ) { scrollViewRef.current.scrollToEnd(); } else { scrollViewRef.current.scrollTo( { x: 0 } ); } }; - const renderHistoryButtons = () => { - const buttons = [ - /* TODO: replace with EditorHistoryRedo and EditorHistoryUndo. */ + + const onInsertBlock = useCallback( + ( blockType ) => () => { + insertBlock( createBlock( blockType ), undefined, undefined, true, { + source: 'inserter_menu', + } ); + }, + [ insertBlock ] + ); + + const renderMediaButtons = ( + , + /> , - ]; - - return isRTL ? buttons.reverse() : buttons; - }; - - const onToggleInserter = useCallback( - ( isOpen ) => { - if ( isOpen ) { - wasNoContentSelected.current = noContentSelected; - } - setIsInserterOpen( isOpen ); - }, - [ noContentSelected ] + /> + + + ); - // Expanded mode should be preserved while the inserter is open. - // This way we prevent style updates during the opening transition. - const useExpandedMode = isInserterOpen - ? wasNoContentSelected.current - : noContentSelected; - /* translators: accessibility text for the editor toolbar */ const toolbarAriaLabel = __( 'Document tools' ); + const shadowColor = usePreferredColorSchemeStyle( + styles[ 'header-toolbar__keyboard-hide-shadow--light' ], + styles[ 'header-toolbar__keyboard-hide-shadow--dark' ] + ); + const showKeyboardButtonStyles = [ + usePreferredColorSchemeStyle( + styles[ 'header-toolbar__keyboard-hide-container' ], + styles[ 'header-toolbar__keyboard-hide-container--dark' ] + ), + shadowStyle, + { + shadowColor: Platform.isAndroid + ? styles[ 'header-toolbar__keyboard-hide-shadow--solid' ].color + : shadowColor.color, + }, + ]; + return ( - - { renderHistoryButtons() } - + + + { noContentSelected && renderMediaButtons } + { showKeyboardHideButton && ( - + { - const { clearSelectedBlock } = dispatch( blockEditorStore ); + const { clearSelectedBlock, insertBlock } = + dispatch( blockEditorStore ); const { togglePostTitleSelection } = dispatch( editorStore ); return { @@ -191,8 +247,8 @@ export default compose( [ clearSelectedBlock(); togglePostTitleSelection( false ); }, + insertBlock, }; } ), withViewportMatch( { isLargeViewport: 'medium' } ), - withPreferredColorScheme, ] )( HeaderToolbar ); diff --git a/packages/edit-post/src/components/header/header-toolbar/style.native.scss b/packages/edit-post/src/components/header/header-toolbar/style.native.scss index 031d082706656a..751f83d3dc63e6 100644 --- a/packages/edit-post/src/components/header/header-toolbar/style.native.scss +++ b/packages/edit-post/src/components/header/header-toolbar/style.native.scss @@ -3,13 +3,13 @@ height: $mobile-header-toolbar-height; flex-direction: row; background-color: $app-background; - border-top-color: #e9eff3; - border-top-width: 1px; + border-top-color: $light-quaternary; + overflow: hidden; } .header-toolbar__container--dark { - background-color: $app-background-dark-alt; - border-top-color: $background-dark-elevated; + background-color: $app-safe-area-background-dark; + border-top-color: $dark-quaternary; } .header-toolbar__container--expanded { @@ -18,6 +18,7 @@ .header-toolbar__scrollable-content { flex-grow: 1; // Fixes RTL issue on Android. + padding-right: 8px; } .header-toolbar__keyboard-hide-container { @@ -27,4 +28,22 @@ width: 44px; justify-content: center; align-items: center; + border-color: transparent; + background-color: $app-background; +} + +.header-toolbar__keyboard-hide-container--dark { + background-color: $app-background-dark-alt; +} + +.header-toolbar__keyboard-hide-shadow--solid { + color: $black; +} + +.header-toolbar__keyboard-hide-shadow--light { + color: $light-quaternary; +} + +.header-toolbar__keyboard-hide-shadow--dark { + color: $light-primary; } diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js index 3f42d4736f57bb..9c9462a641dd33 100644 --- a/packages/edit-post/src/components/header/index.js +++ b/packages/edit-post/src/components/header/index.js @@ -32,22 +32,17 @@ const slideX = { function Header( { setEntitiesSavedStatesCallback } ) { const isLargeViewport = useViewportMatch( 'large' ); - const { - hasActiveMetaboxes, - isPublishSidebarOpened, - isSaving, - showIconLabels, - } = useSelect( - ( select ) => ( { - hasActiveMetaboxes: select( editPostStore ).hasMetaBoxes(), - isPublishSidebarOpened: - select( editPostStore ).isPublishSidebarOpened(), - isSaving: select( editPostStore ).isSavingMetaBoxes(), - showIconLabels: - select( editPostStore ).isFeatureActive( 'showIconLabels' ), - } ), - [] - ); + const { hasActiveMetaboxes, isPublishSidebarOpened, showIconLabels } = + useSelect( + ( select ) => ( { + hasActiveMetaboxes: select( editPostStore ).hasMetaBoxes(), + isPublishSidebarOpened: + select( editPostStore ).isPublishSidebarOpened(), + showIconLabels: + select( editPostStore ).isFeatureActive( 'showIconLabels' ), + } ), + [] + ); return (
      @@ -82,19 +77,14 @@ function Header( { setEntitiesSavedStatesCallback } ) { // when the publish sidebar has been closed. ) } - + { - return { + } = useSelect( + ( select ) => ( { publishSidebarOpened: select( editPostStore ).isPublishSidebarOpened(), hasActiveMetaboxes: select( editPostStore ).hasMetaBoxes(), - isSavingMetaBoxes: select( editPostStore ).isSavingMetaBoxes(), hasNonPostEntityChanges: select( editorStore ).hasNonPostEntityChanges(), - }; - }, [] ); + } ), + [] + ); const openEntitiesSavedStates = useCallback( () => setEntitiesSavedStatesCallback( true ), @@ -57,7 +56,6 @@ export default function ActionsPanel( { diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 15bc017900daa2..b7aa8bcd025af7 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -13,6 +13,7 @@ import { EditorNotices, EditorKeyboardShortcutsRegister, EditorSnackbars, + PostSyncStatusModal, store as editorStore, } from '@wordpress/editor'; import { useSelect, useDispatch } from '@wordpress/data'; @@ -291,6 +292,7 @@ function Layout( { styles } ) { + diff --git a/packages/edit-post/src/components/layout/style.native.scss b/packages/edit-post/src/components/layout/style.native.scss index faee2799e54eb8..765314bf15955c 100644 --- a/packages/edit-post/src/components/layout/style.native.scss +++ b/packages/edit-post/src/components/layout/style.native.scss @@ -6,7 +6,7 @@ } .containerDark { - background-color: $app-background-dark-alt; + background-color: $app-safe-area-background-dark; } .background { diff --git a/packages/edit-post/src/components/preferences-modal/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/preferences-modal/test/__snapshots__/index.js.snap index 182531ee01c02f..32254bb91c6955 100644 --- a/packages/edit-post/src/components/preferences-modal/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/preferences-modal/test/__snapshots__/index.js.snap @@ -590,6 +590,8 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active } .emotion-13 { + font-size: 13px; + font-family: inherit; -webkit-appearance: none; -moz-appearance: none; -ms-appearance: none; @@ -616,12 +618,18 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9)); } +.emotion-13:focus { + box-shadow: none; + outline: none; +} + .emotion-13:focus-visible { box-shadow: 0 0 0 var( --wp-admin-border-width-focus ) var( - --wp-components-color-accent, - var( --wp-admin-theme-color, var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9)) ) - ); + --wp-components-color-accent, + var( --wp-admin-theme-color, var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9)) ) + ); outline: 2px solid transparent; + outline-offset: 0; } .emotion-15 { 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 5cc9b70bf9ac31..d0633f26cf254e 100644 --- a/packages/edit-post/src/components/sidebar/post-status/index.js +++ b/packages/edit-post/src/components/sidebar/post-status/index.js @@ -58,6 +58,7 @@ function PostStatus( { isOpened, onTogglePanel } ) { marginTop: '16px', } } spacing={ 4 } + wrap > diff --git a/packages/edit-post/src/components/sidebar/post-trash/index.js b/packages/edit-post/src/components/sidebar/post-trash/index.js index 885be537952c0b..d77c7a6d82988c 100644 --- a/packages/edit-post/src/components/sidebar/post-trash/index.js +++ b/packages/edit-post/src/components/sidebar/post-trash/index.js @@ -2,14 +2,11 @@ * WordPress dependencies */ import { PostTrash as PostTrashLink, PostTrashCheck } from '@wordpress/editor'; -import { FlexItem } from '@wordpress/components'; export default function PostTrash() { return ( - - - + ); } diff --git a/packages/edit-post/src/components/visual-editor/header.native.js b/packages/edit-post/src/components/visual-editor/header.native.js index 20ed9ae5d8164b..f7679e531ec9ea 100644 --- a/packages/edit-post/src/components/visual-editor/header.native.js +++ b/packages/edit-post/src/components/visual-editor/header.native.js @@ -16,23 +16,9 @@ import { useEditorWrapperStyles, } from '@wordpress/block-editor'; -/** - * Internal dependencies - */ -import styles from './style.scss'; - const Header = memo( - function EditorHeader( { - editTitle, - setTitleRef, - title, - getStylesFromColorScheme, - } ) { + function EditorHeader( { editTitle, setTitleRef, title } ) { const [ wrapperStyles ] = useEditorWrapperStyles(); - const blockHolderFocusedStyle = getStylesFromColorScheme( - styles.blockHolderFocused, - styles.blockHolderFocusedDark - ); return ( diff --git a/packages/edit-post/src/components/visual-editor/style.native.scss b/packages/edit-post/src/components/visual-editor/style.native.scss deleted file mode 100644 index 7475caa801a2b6..00000000000000 --- a/packages/edit-post/src/components/visual-editor/style.native.scss +++ /dev/null @@ -1,18 +0,0 @@ -.blockHolderFullBordered { - border-top-width: $block-selected-border-width; - border-bottom-width: $block-selected-border-width; - border-left-width: $block-selected-border-width; - border-right-width: $block-selected-border-width; - border-radius: 4px; - border-style: solid; - margin-left: 4px; - margin-right: 4px; -} - -.blockHolderFocused { - border-color: $blue-wordpress; -} - -.blockHolderFocusedDark { - border-color: $blue-30; -} diff --git a/packages/edit-post/src/components/visual-editor/test/__snapshots__/index.native.js.snap b/packages/edit-post/src/components/visual-editor/test/__snapshots__/index.native.js.snap new file mode 100644 index 00000000000000..de7091fa947357 --- /dev/null +++ b/packages/edit-post/src/components/visual-editor/test/__snapshots__/index.native.js.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`when nothing is selected media buttons and picker display correctly 1`] = ` +" +

      First example paragraph.

      + + + +

      Second example paragraph.

      + + + + +" +`; diff --git a/packages/edit-post/src/components/visual-editor/test/index.native.js b/packages/edit-post/src/components/visual-editor/test/index.native.js index af07e4309ab691..8c6e041880a830 100644 --- a/packages/edit-post/src/components/visual-editor/test/index.native.js +++ b/packages/edit-post/src/components/visual-editor/test/index.native.js @@ -1,11 +1,12 @@ /** * External dependencies */ -import { initializeEditor, fireEvent } from 'test/helpers'; +import { initializeEditor, getEditorHtml, fireEvent } from 'test/helpers'; /** * WordPress dependencies */ +import { Platform } from '@wordpress/element'; import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; import { registerCoreBlocks } from '@wordpress/block-library'; @@ -21,6 +22,12 @@ afterAll( () => { } ); } ); +const MEDIA_OPTIONS = [ + 'Choose from device', + 'Take a Photo', + 'WordPress Media Library', +]; + const initialHtml = `

      First example paragraph.

      @@ -63,6 +70,38 @@ describe( 'when title is focused', () => { screen.getAllByLabelText( /Paragraph Block. Row 3/ )[ 0 ] ).toBeDefined(); } ); + + it( 'media blocks should be displayed', async () => { + const screen = await initializeEditor( { + initialHtml, + } ); + + // Focus first block + fireEvent.press( + screen.getAllByLabelText( /Paragraph Block. Row 1/ )[ 0 ] + ); + + // Focus title + fireEvent( + screen.getAllByLabelText( 'Post title. test' )[ 0 ], + 'select' + ); + + // Assert that the media buttons are visible + const imageButton = await screen.findByTestId( 'insert-image-button' ); + expect( imageButton ).toBeVisible(); + + const videoButton = await screen.findByTestId( 'insert-video-button' ); + expect( videoButton ).toBeVisible(); + + const galleryButton = await screen.findByTestId( + 'insert-gallery-button' + ); + expect( galleryButton ).toBeVisible(); + + const audioButton = await screen.findByTestId( 'insert-audio-button' ); + expect( audioButton ).toBeVisible(); + } ); } ); describe( 'when title is no longer focused', () => { @@ -101,4 +140,82 @@ describe( 'when title is no longer focused', () => { screen.getAllByLabelText( /Heading Block. Row 3/ )[ 0 ] ).toBeDefined(); } ); + + it( 'media blocks should not be displayed', async () => { + const screen = await initializeEditor( { + initialHtml, + } ); + + // Focus first block + fireEvent.press( + screen.getAllByLabelText( /Paragraph Block. Row 1/ )[ 0 ] + ); + + // Focus title + fireEvent( + screen.getAllByLabelText( 'Post title. test' )[ 0 ], + 'select' + ); + + // Focus last block + fireEvent.press( + screen.getAllByLabelText( /Paragraph Block. Row 2/ )[ 0 ] + ); + + // Assert that the media buttons are not visible + const imageButton = screen.queryByTestId( 'insert-image-button' ); + expect( imageButton ).toBeNull(); + + const videoButton = screen.queryByTestId( 'insert-video-button' ); + expect( videoButton ).toBeNull(); + + const galleryButton = screen.queryByTestId( 'insert-gallery-button' ); + expect( galleryButton ).toBeNull(); + + const audioButton = screen.queryByTestId( 'insert-audio-button' ); + expect( audioButton ).toBeNull(); + } ); +} ); + +describe( 'when nothing is selected', () => { + it( 'media buttons and picker display correctly', async () => { + const screen = await initializeEditor( { + initialHtml, + } ); + + const { getByText, getByTestId } = screen; + + // Check that the gallery button is visible within the toolbar + const galleryButton = await screen.queryByTestId( + 'insert-gallery-button' + ); + expect( galleryButton ).toBeVisible(); + + // Press the toolbar Gallery button + fireEvent.press( galleryButton ); + + // Expect the block to be created + expect( + screen.getAllByLabelText( /Gallery Block. Row 3/ )[ 0 ] + ).toBeDefined(); + + expect( getByText( 'Choose images' ) ).toBeVisible(); + MEDIA_OPTIONS.forEach( ( option ) => + expect( getByText( option ) ).toBeVisible() + ); + + // Dismiss the picker + if ( Platform.isIOS ) { + fireEvent.press( getByText( 'Cancel' ) ); + } else { + fireEvent( getByTestId( 'media-options-picker' ), 'backdropPress' ); + } + + // Expect the Gallery block to remain + expect( + screen.getAllByLabelText( /Gallery Block. Row 3/ )[ 0 ] + ).toBeDefined(); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/edit-post/src/plugins/index.js b/packages/edit-post/src/plugins/index.js index e3bd1b2dd72bda..aa663659acbdc1 100644 --- a/packages/edit-post/src/plugins/index.js +++ b/packages/edit-post/src/plugins/index.js @@ -2,6 +2,9 @@ * WordPress dependencies */ import { MenuItem, VisuallyHidden } from '@wordpress/components'; +import { store as coreStore } from '@wordpress/core-data'; +import { store as editorStore } from '@wordpress/editor'; +import { useSelect } from '@wordpress/data'; import { external } from '@wordpress/icons'; import { __ } from '@wordpress/i18n'; import { registerPlugin } from '@wordpress/plugins'; @@ -15,6 +18,34 @@ import KeyboardShortcutsHelpMenuItem from './keyboard-shortcuts-help-menu-item'; import ToolsMoreMenuGroup from '../components/header/tools-more-menu-group'; import WelcomeGuideMenuItem from './welcome-guide-menu-item'; +function ManagePatternsMenuItem() { + const url = useSelect( ( select ) => { + const { canUser } = select( coreStore ); + const { getEditorSettings } = select( editorStore ); + + const isBlockTheme = getEditorSettings().__unstableIsBlockBasedTheme; + const defaultUrl = addQueryArgs( 'edit.php', { + post_type: 'wp_block', + } ); + const patternsUrl = addQueryArgs( 'site-editor.php', { + path: '/patterns', + } ); + + // The site editor and templates both check whether the user has + // edit_theme_options capabilities. We can leverage that here and not + // display the manage patterns link if the user can't access it. + return canUser( 'read', 'templates' ) && isBlockTheme + ? patternsUrl + : defaultUrl; + }, [] ); + + return ( + + { __( 'Manage patterns' ) } + + ); +} + registerPlugin( 'edit-post', { render() { return ( @@ -22,14 +53,7 @@ registerPlugin( 'edit-post', { { ( { onClose } ) => ( <> - - { __( 'Manage Patterns' ) } - + diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index f938a9837516e0..0ee1efb62b02ea 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -11,6 +11,7 @@ import { store as coreStore } from '@wordpress/core-data'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { store as editorStore } from '@wordpress/editor'; import deprecated from '@wordpress/deprecated'; +import { addFilter } from '@wordpress/hooks'; /** * Internal dependencies @@ -567,33 +568,23 @@ export const initializeMetaBoxes = metaBoxesInitialized = true; - let wasSavingPost = registry.select( editorStore ).isSavingPost(); - let wasAutosavingPost = registry - .select( editorStore ) - .isAutosavingPost(); - - // Save metaboxes when performing a full save on the post. - registry.subscribe( async () => { - const isSavingPost = registry.select( editorStore ).isSavingPost(); - const isAutosavingPost = registry - .select( editorStore ) - .isAutosavingPost(); - - // Save metaboxes on save completion, except for autosaves. - const shouldTriggerMetaboxesSave = - wasSavingPost && - ! wasAutosavingPost && - ! isSavingPost && - select.hasMetaBoxes(); - - // Save current state for next inspection. - wasSavingPost = isSavingPost; - wasAutosavingPost = isAutosavingPost; - - if ( shouldTriggerMetaboxesSave ) { - await dispatch.requestMetaBoxUpdates(); - } - } ); + // Save metaboxes on save completion, except for autosaves. + addFilter( + 'editor.__unstableSavePost', + 'core/edit-post/save-metaboxes', + ( previous, options ) => + previous.then( () => { + if ( options.isAutosave ) { + return; + } + + if ( ! select.hasMetaBoxes() ) { + return; + } + + return dispatch.requestMetaBoxUpdates(); + } ) + ); dispatch( { type: 'META_BOXES_INITIALIZED', diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss index d33a5ae22c0fd9..fac5deef18f717 100644 --- a/packages/edit-post/src/style.scss +++ b/packages/edit-post/src/style.scss @@ -41,13 +41,7 @@ } } -// In order to use mix-blend-mode, this element needs to have an explicitly set background-color -// We scope it to .wp-toolbar to be wp-admin only, to prevent bleed into other implementations -html.wp-toolbar { - background: $white; -} - -body.block-editor-page { +body.js.block-editor-page { @include wp-admin-reset( ".block-editor" ); } diff --git a/packages/edit-site/CHANGELOG.md b/packages/edit-site/CHANGELOG.md index 55497a651f92d7..cc77bca0e7375c 100644 --- a/packages/edit-site/CHANGELOG.md +++ b/packages/edit-site/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.14.0 (2023-07-05) + ## 5.13.0 (2023-06-23) ### Enhancements diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 0b5560a277839e..06e52ed9fd53e7 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-site", - "version": "5.13.0", + "version": "5.14.0", "description": "Edit Site Page module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -71,7 +71,6 @@ "downloadjs": "^1.4.7", "fast-deep-equal": "^3.1.3", "is-plain-object": "^5.0.0", - "lodash": "^4.17.21", "memize": "^2.1.0", "react-autosize-textarea": "^7.1.0", "rememo": "^4.0.2", diff --git a/packages/edit-site/src/components/add-new-page/index.js b/packages/edit-site/src/components/add-new-page/index.js index 0c8efa3dfaa0ba..09d2e17ff94d1a 100644 --- a/packages/edit-site/src/components/add-new-page/index.js +++ b/packages/edit-site/src/components/add-new-page/index.js @@ -72,9 +72,6 @@ export default function AddNewPageModal( { onSave, onClose } ) {
      { + const settings = select( editSiteStore ).getSettings(); + return !! settings.supportsTemplatePartsMode; + }, [] ); function handleCreatePattern( { pattern, categoryId } ) { setShowPatternModal( false ); @@ -51,21 +57,26 @@ export default function AddNewPattern() { setShowTemplatePartModal( false ); } + const controls = [ + { + icon: symbol, + onClick: () => setShowPatternModal( true ), + title: __( 'Create pattern' ), + }, + ]; + + if ( ! isTemplatePartsMode ) { + controls.push( { + icon: symbolFilled, + onClick: () => setShowTemplatePartModal( true ), + title: __( 'Create template part' ), + } ); + } + return ( <> setShowTemplatePartModal( true ), - title: __( 'Create template part' ), - }, - { - icon: symbol, - onClick: () => setShowPatternModal( true ), - title: __( 'Create pattern' ), - }, - ] } + controls={ controls } toggleProps={ { as: SidebarButton, } } diff --git a/packages/edit-site/src/components/add-new-template/utils.js b/packages/edit-site/src/components/add-new-template/utils.js index 8de37c1f64632c..1869cdb72f5929 100644 --- a/packages/edit-site/src/components/add-new-template/utils.js +++ b/packages/edit-site/src/components/add-new-template/utils.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { get } from 'lodash'; - /** * WordPress dependencies */ @@ -20,6 +15,14 @@ import { blockMeta, post, archive } from '@wordpress/icons'; * @property {string} name The entity's name. */ +const getValueFromObjectPath = ( object, path ) => { + let value = object; + path.split( '.' ).forEach( ( fieldName ) => { + value = value?.[ fieldName ]; + } ); + return value; +}; + /** * Helper util to map records to add a `name` prop from a * provided path, in order to handle all entities in the same @@ -32,7 +35,7 @@ import { blockMeta, post, archive } from '@wordpress/icons'; export const mapToIHasNameAndId = ( entities, path ) => { return ( entities || [] ).map( ( entity ) => ( { ...entity, - name: decodeEntities( get( entity, path ) ), + name: decodeEntities( getValueFromObjectPath( entity, path ) ), } ) ); }; diff --git a/packages/edit-site/src/components/block-editor/constants.js b/packages/edit-site/src/components/block-editor/constants.js index 9f076eea1ca311..ed88c3e14c9473 100644 --- a/packages/edit-site/src/components/block-editor/constants.js +++ b/packages/edit-site/src/components/block-editor/constants.js @@ -1 +1,5 @@ -export const FOCUSABLE_ENTITIES = [ 'wp_template_part', 'wp_navigation' ]; +export const FOCUSABLE_ENTITIES = [ + 'wp_template_part', + 'wp_navigation', + 'wp_block', +]; diff --git a/packages/edit-site/src/components/block-editor/editor-canvas.js b/packages/edit-site/src/components/block-editor/editor-canvas.js index 34c23cc699bc28..a8bbe75e0261b5 100644 --- a/packages/edit-site/src/components/block-editor/editor-canvas.js +++ b/packages/edit-site/src/components/block-editor/editor-canvas.js @@ -88,7 +88,7 @@ function EditorCanvas( { enableResizing, settings, children, ...props } ) { enableResizing ? 'min-height:0!important;' : '' }}body{position:relative; ${ canvasMode === 'view' - ? 'cursor: pointer; height: 100vh' + ? 'cursor: pointer; min-height: 100vh;' : '' }}}` } diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js index fcd28948ccbb3f..e2d9e2680b263d 100644 --- a/packages/edit-site/src/components/block-editor/index.js +++ b/packages/edit-site/src/components/block-editor/index.js @@ -1,7 +1,3 @@ -/** - * External dependencies - */ - /** * WordPress dependencies */ diff --git a/packages/edit-site/src/components/block-editor/use-site-editor-settings.js b/packages/edit-site/src/components/block-editor/use-site-editor-settings.js index 732f662f5ddcad..b215f5c34273ea 100644 --- a/packages/edit-site/src/components/block-editor/use-site-editor-settings.js +++ b/packages/edit-site/src/components/block-editor/use-site-editor-settings.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useSelect } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; import { useMemo } from '@wordpress/element'; import { store as coreStore } from '@wordpress/core-data'; /** @@ -11,16 +11,73 @@ import { store as editSiteStore } from '../../store'; import { unlock } from '../../lock-unlock'; import inserterMediaCategories from './inserter-media-categories'; -export default function useSiteEditorSettings( templateType ) { - const { storedSettings, canvasMode } = useSelect( ( select ) => { - const { getSettings, getCanvasMode } = unlock( - select( editSiteStore ) - ); - return { - storedSettings: getSettings(), - canvasMode: getCanvasMode(), - }; - }, [] ); +function useArchiveLabel( templateSlug ) { + const taxonomyMatches = templateSlug?.match( + /^(category|tag|taxonomy-([^-]+))$|^(((category|tag)|taxonomy-([^-]+))-(.+))$/ + ); + let taxonomy; + let term; + if ( taxonomyMatches ) { + // If is for a all taxonomies of a type + if ( taxonomyMatches[ 1 ] ) { + taxonomy = taxonomyMatches[ 2 ] + ? taxonomyMatches[ 2 ] + : taxonomyMatches[ 1 ]; + } + // If is for a all taxonomies of a type + else if ( taxonomyMatches[ 3 ] ) { + taxonomy = taxonomyMatches[ 6 ] + ? taxonomyMatches[ 6 ] + : taxonomyMatches[ 4 ]; + term = taxonomyMatches[ 7 ]; + } + taxonomy = taxonomy === 'tag' ? 'post_tag' : taxonomy; + + //getTaxonomy( 'category' ); + //wp.data.select('core').getEntityRecords( 'taxonomy', 'category', {slug: 'newcat'} ); + } + return useSelect( + ( select ) => { + const { getEntityRecords, getTaxonomy } = select( coreStore ); + let archiveTypeLabel; + let archiveNameLabel; + if ( taxonomy ) { + archiveTypeLabel = + getTaxonomy( taxonomy )?.labels?.singular_name; + } + if ( term ) { + const records = getEntityRecords( 'taxonomy', taxonomy, { + slug: term, + per_page: 1, + } ); + if ( records && records[ 0 ] ) { + archiveNameLabel = records[ 0 ].name; + } + } + return { + archiveTypeLabel, + archiveNameLabel, + }; + }, + [ taxonomy, term ] + ); +} + +export default function useSiteEditorSettings() { + const { setIsInserterOpened } = useDispatch( editSiteStore ); + const { storedSettings, canvasMode, templateType } = useSelect( + ( select ) => { + const { getSettings, getCanvasMode, getEditedPostType } = unlock( + select( editSiteStore ) + ); + return { + storedSettings: getSettings( setIsInserterOpened ), + canvasMode: getCanvasMode(), + templateType: getEditedPostType(), + }; + }, + [ setIsInserterOpened ] + ); const settingsBlockPatterns = storedSettings.__experimentalAdditionalBlockPatterns ?? // WP 6.0 @@ -29,14 +86,27 @@ export default function useSiteEditorSettings( templateType ) { storedSettings.__experimentalAdditionalBlockPatternCategories ?? // WP 6.0 storedSettings.__experimentalBlockPatternCategories; // WP 5.9 - const { restBlockPatterns, restBlockPatternCategories } = useSelect( - ( select ) => ( { - restBlockPatterns: select( coreStore ).getBlockPatterns(), - restBlockPatternCategories: - select( coreStore ).getBlockPatternCategories(), - } ), - [] - ); + const { restBlockPatterns, restBlockPatternCategories, templateSlug } = + useSelect( ( select ) => { + const { getEditedPostType, getEditedPostId } = + select( editSiteStore ); + const { getEditedEntityRecord } = select( coreStore ); + const usedPostType = getEditedPostType(); + const usedPostId = getEditedPostId(); + const _record = getEditedEntityRecord( + 'postType', + usedPostType, + usedPostId + ); + return { + restBlockPatterns: select( coreStore ).getBlockPatterns(), + restBlockPatternCategories: + select( coreStore ).getBlockPatternCategories(), + templateSlug: _record.slug, + }; + }, [] ); + const archiveLabels = useArchiveLabel( templateSlug ); + const blockPatterns = useMemo( () => [ @@ -82,6 +152,15 @@ export default function useSiteEditorSettings( templateType ) { __experimentalBlockPatterns: blockPatterns, __experimentalBlockPatternCategories: blockPatternCategories, focusMode: canvasMode === 'view' && focusMode ? false : focusMode, + __experimentalArchiveTitleTypeLabel: archiveLabels.archiveTypeLabel, + __experimentalArchiveTitleNameLabel: archiveLabels.archiveNameLabel, }; - }, [ storedSettings, blockPatterns, blockPatternCategories, canvasMode ] ); + }, [ + storedSettings, + blockPatterns, + blockPatternCategories, + canvasMode, + archiveLabels.archiveTypeLabel, + archiveLabels.archiveNameLabel, + ] ); } diff --git a/packages/edit-site/src/components/canvas-spinner/style.scss b/packages/edit-site/src/components/canvas-spinner/style.scss index 2f0626b80363fe..22b1b856257424 100644 --- a/packages/edit-site/src/components/canvas-spinner/style.scss +++ b/packages/edit-site/src/components/canvas-spinner/style.scss @@ -2,10 +2,24 @@ width: 100%; height: 100%; display: flex; + opacity: 0; align-items: center; justify-content: center; + animation: 0.5s ease 1s edit-site-canvas-spinner__fade-in-animation; + animation-fill-mode: forwards; + @include reduce-motion("animation"); + circle { stroke: rgba($black, 0.3); } } + +@keyframes edit-site-canvas-spinner__fade-in-animation { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/packages/edit-site/src/components/code-editor/code-editor-text-area.js b/packages/edit-site/src/components/code-editor/code-editor-text-area.js deleted file mode 100644 index 1a907ca8e59f68..00000000000000 --- a/packages/edit-site/src/components/code-editor/code-editor-text-area.js +++ /dev/null @@ -1,84 +0,0 @@ -/** - * External dependencies - */ -import Textarea from 'react-autosize-textarea'; - -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { useEffect, useState, useRef } from '@wordpress/element'; -import { useInstanceId } from '@wordpress/compose'; -import { VisuallyHidden } from '@wordpress/components'; - -export default function CodeEditorTextArea( { value, onChange, onInput } ) { - const [ stateValue, setStateValue ] = useState( value ); - const [ isDirty, setIsDirty ] = useState( false ); - const instanceId = useInstanceId( CodeEditorTextArea ); - const valueRef = useRef(); - - if ( ! isDirty && stateValue !== value ) { - setStateValue( value ); - } - - /** - * Handles a textarea change event to notify the onChange prop callback and - * reflect the new value in the component's own state. This marks the start - * of the user's edits, if not already changed, preventing future props - * changes to value from replacing the rendered value. This is expected to - * be followed by a reset to dirty state via `stopEditing`. - * - * @see stopEditing - * - * @param {Event} event Change event. - */ - const onChangeHandler = ( event ) => { - const newValue = event.target.value; - onInput( newValue ); - setStateValue( newValue ); - setIsDirty( true ); - valueRef.current = newValue; - }; - - /** - * Function called when the user has completed their edits, responsible for - * ensuring that changes, if made, are surfaced to the onPersist prop - * callback and resetting dirty state. - */ - const stopEditing = () => { - if ( isDirty ) { - onChange( stateValue ); - setIsDirty( false ); - } - }; - - // Ensure changes aren't lost when component unmounts. - useEffect( () => { - return () => { - if ( valueRef.current ) { - onChange( valueRef.current ); - } - }; - }, [] ); - - return ( - <> - - { __( 'Type text or HTML' ) } - -