diff --git a/.eslintrc.js b/.eslintrc.js index b8012f2686cbb8..4063bb42cf32ab 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -81,6 +81,7 @@ const restrictedImports = [ 'flowRight', 'forEach', 'fromPairs', + 'groupBy', 'has', 'identity', 'includes', @@ -104,6 +105,7 @@ const restrictedImports = [ 'lowerCase', 'map', 'mapKeys', + 'mapValues', 'maxBy', 'memoize', 'merge', @@ -350,7 +352,12 @@ module.exports = { }, }, { - files: [ 'packages/components/src/**/*.[tj]s?(x)' ], + files: [ + // Components package. + 'packages/components/src/**/*.[tj]s?(x)', + // Navigation block. + 'packages/block-library/src/navigation/**/*.[tj]s?(x)', + ], excludedFiles: [ ...developmentFiles ], rules: { 'react-hooks/exhaustive-deps': 'error', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index cc39bde3d2d93f..d578b20c6cb61e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,5 +1,5 @@ # Documentation -/docs @ajitbohra @ryanwelcher @juanmaguitar @fabiankaegy +/docs @ajitbohra @ryanwelcher @juanmaguitar @fabiankaegy @ndiego # Schemas /schemas/json @ajlende @@ -57,14 +57,13 @@ # Tooling /bin @ntwb @nerrad @ajitbohra /bin/api-docs @ntwb @nerrad @ajitbohra -/docs/tool @ajitbohra +/docs/tool @ajitbohra @ndiego /packages/babel-plugin-import-jsx-pragma @ntwb @nerrad @ajitbohra /packages/babel-plugin-makepot @ntwb @nerrad @ajitbohra /packages/babel-preset-default @gziolo @ntwb @nerrad @ajitbohra /packages/browserslist-config @ntwb @nerrad @ajitbohra /packages/create-block @gziolo @ryanwelcher /packages/create-block-tutorial-template @gziolo @ryanwelcher -/packages/custom-templated-path-webpack-plugin @ntwb @nerrad @ajitbohra /packages/dependency-extraction-webpack-plugin @gziolo /packages/docgen /packages/e2e-test-utils @ntwb @nerrad @ajitbohra @@ -74,7 +73,6 @@ /packages/jest-console @gziolo @ntwb @nerrad @ajitbohra /packages/jest-preset-default @gziolo @ntwb @nerrad @ajitbohra /packages/jest-puppeteer-axe @gziolo @ntwb @nerrad @ajitbohra -/packages/library-export-default-webpack-plugin @ntwb @nerrad @ajitbohra /packages/npm-package-json-lint-config @gziolo @ntwb @nerrad @ajitbohra /packages/postcss-themes @ntwb @nerrad @ajitbohra /packages/prettier-config @ntwb @gziolo @@ -117,9 +115,9 @@ /packages/plugins @gziolo @adamsilverstein # Rich Text -/packages/format-library @ellatrix @fluiddot -/packages/rich-text @ellatrix @fluiddot -/packages/block-editor/src/components/rich-text @ellatrix @fluiddot +/packages/format-library @ellatrix @fluiddot @dcalhoun +/packages/rich-text @ellatrix @fluiddot @dcalhoun +/packages/block-editor/src/components/rich-text @ellatrix @fluiddot @dcalhoun # Project Management /.github @@ -127,7 +125,7 @@ /packages/report-flaky-tests @kevin940726 # wp-env -/packages/env @noahtallen +/packages/env @noahtallen @ObliviousHarmony # PHP /lib @spacedmonkey diff --git a/.github/ISSUE_TEMPLATE/New_release.md b/.github/ISSUE_TEMPLATE/New_release.md new file mode 100644 index 00000000000000..d6732a659731f6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/New_release.md @@ -0,0 +1,53 @@ +--- +name: Gutenberg Release +about: A checklist for the Gutenberg plugin release process +--- + +This issue is to provide visibility on the progress of the release process of Gutenberg VERSION_NUMBER and to centralize any conversations about it. The ultimate goal of this issue is to keep the reference of the steps, resources, work, and conversations about this release so it can be helpful for the next contributors releasing a new Gutenberg version. + +- Gutenberg version to release: VERSION_NUMBER ([milestone](ADD_LINK)) +- Release Manager (a.k.a. Release Lead): +- Release Date VERSION_NUMBER RC: ADD DATE +- Release Date VERSION_NUMBER: ADD DATE +- Previous version change log (as a reference): [15.3](https://github.com/WordPress/gutenberg/releases/tag/v15.3.0) + +## Resources + +- 📖 Read: [Gutenberg Release Process](https://developer.wordpress.org/block-editor/contributors/code/release/) +- 📽 Watch: [Gutenberg Plugin: New Release Workflow](https://www.youtube.com/watch?v=TnSgJd3zpJY) +- 📽 Watch: [Creating the Gutenberg plugin v12.0 Release Candidate](https://www.youtube.com/watch?v=FLkLHKecxWg) +- 📽 Watch: [Gutenberg plugin v12.0.0 Release Party!](https://www.youtube.com/watch?v=4SDtpVPDsLc) + +## Checklist + +### RC Day - {Weekday, Month, Date} + +- [ ] _Optional:_ Attend `#core-editor` meeting (14:00UTC) +- [ ] Post a message in `#core-editor` channel to let folks know you are starting the RC release process +- [ ] Organize and Label PRs on the relevant milestone +- [ ] Start the release process by triggering the `rc` [workflow](https://developer.wordpress.org/block-editor/contributors/code/release/#running-workflow) +- [ ] [Update the created Draft Release accordingly](https://developer.wordpress.org/block-editor/contributors/code/release/#view-the-release-draft) +- [ ] [Curate the changelog](https://developer.wordpress.org/block-editor/contributors/code/release/#1-curating-the-changelog) before publishing +- [ ] Publish Release +- [ ] Announce in `#core-editor` channel that RC1 has been released and is ready for testing +- [ ] Ping any other relevant channels announcing that the RC is available +- [ ] Create Draft of Release post on Make Core blog _(initial draft in [Google doc](https://docs.google.com/document/d/1D-MTOCmL9eMlP9TDTXqlzuKVOg_ghCPm9_whHFViqMk/edit))_ + +### Between RC and Release + +- [ ] Post a reminder in #core-editor for backporting PRs to RC (~Label Backport to Gutenberg RC) +- [ ] If there are any PRs marked as [Backport to RC](https://github.com/WordPress/gutenberg/pulls?q=is%3Apr+label%3A%22Backport+to+Gutenberg+RC%22+is%3Aclosed), run the [cherry-pick command to apply them](https://developer.wordpress.org/block-editor/contributors/code/release/auto-cherry-picking/#how-can-i-use-it-for-a-gutenberg-plugin-release) to the release branch. **This needs to be run locally** +- [ ] [Draft Release Post Highlights and Change Log](https://docs.google.com/document/d/1D-MTOCmL9eMlP9TDTXqlzuKVOg_ghCPm9_whHFViqMk/edit) +- [ ] Get assets from [Design Team](https://make.wordpress.org/design/) for the post +- [ ] Reach out to Highlight Authors to draft sections (if necessary) + +### Release Day - {Weekday, Month, Date} + +- [ ] Post a message in `#core-editor` channel to let folks know you are starting the release process +- [ ] Start the release process by triggering the `stable` [workflow](https://developer.wordpress.org/block-editor/contributors/code/release/#running-workflow) +- [ ] Update the created Draft Release accordingly. Typically by copy/pasting the last RC release notes and add any changes/updates as needed. +- [ ] Publish Release +- [ ] Trigger the update to the plugin directory. _(Get approval from a member of [Gutenberg Release team](https://github.com/orgs/WordPress/teams/gutenberg-release/members) if necessary)_ +- [ ] Announce in `#core-editor` channel that the plugin has been released +- [ ] Reach out to other contributors to help get the post reviewed +- [ ] Publish Release post on Make Core blog diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md index 02165e02a7f955..cc6b8e4d613513 100644 --- a/.github/SUPPORT.md +++ b/.github/SUPPORT.md @@ -9,4 +9,4 @@ Welcome to Gutenberg, a WordPress project. We hope you join us in creating the f * Join us on Slack for real-time communication, it is where maintainers coordinate around the project. To get started using Slack, see: https://make.wordpress.org/chat/ -* For general WordPress support with the core editor, see the [WordPress.org support forums](https://wordpress.org/support/) — it is highly active and well maintained. +* For general WordPress support with the core editor, see the [WordPress.org support forums](https://wordpress.org/support/forums/) — it is highly active and well maintained. diff --git a/.github/setup-node/action.yml b/.github/setup-node/action.yml new file mode 100644 index 00000000000000..22cb81618a1efb --- /dev/null +++ b/.github/setup-node/action.yml @@ -0,0 +1,46 @@ +name: 'Setup Node.js and install npm dependencies' +description: 'Configure Node.js and install npm dependencies while managing all aspects of caching.' +inputs: + node-version: + description: 'Optional. The Node.js version to use. When not specified, the version specified in .nvmrc will be used.' + required: false + type: string + +runs: + using: 'composite' + steps: + - name: Use desired version of Node.js + uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 + with: + node-version-file: '.nvmrc' + node-version: ${{ inputs.node-version }} + cache: npm + + - name: Get Node.js and npm version + id: node-version + run: | + echo "NODE_VERSION=$(node -v)" >> $GITHUB_OUTPUT + shell: bash + + - name: Cache node_modules + id: cache-node_modules + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 + with: + path: '**/node_modules' + key: node_modules-${{ runner.os }}-${{ steps.node-version.outputs.NODE_VERSION }}-${{ hashFiles('package-lock.json') }} + + - name: Install npm dependencies + if: ${{ steps.cache-node_modules.outputs.cache-hit != 'true' }} + run: npm ci + shell: bash + + # On cache hit, we run the post-install script to match the native `npm ci` behavior. + # An example of this is to patch `node_modules` using patch-package. + - name: Post-install + if: ${{ steps.cache-node_modules.outputs.cache-hit == 'true' }} + run: | + # Run the post-install script for the root project. + npm run postinstall + # Run the post-install scripts for workspaces. + npx lerna run postinstall + shell: bash diff --git a/.github/workflows/build-plugin-zip.yml b/.github/workflows/build-plugin-zip.yml index 6e915d0ffddd27..9052f1689c9216 100644 --- a/.github/workflows/build-plugin-zip.yml +++ b/.github/workflows/build-plugin-zip.yml @@ -69,7 +69,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 with: token: ${{ secrets.GUTENBERG_TOKEN }} @@ -164,11 +164,11 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 with: ref: ${{ needs.bump-version.outputs.release_branch || github.ref }} - - name: Use desired version of NodeJS + - name: Use desired version of Node.js uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 with: node-version-file: '.nvmrc' @@ -219,7 +219,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 with: fetch-depth: 2 ref: ${{ needs.bump-version.outputs.release_branch }} @@ -307,13 +307,13 @@ jobs: if: ${{ endsWith( needs.bump-version.outputs.new_version, '-rc.1' ) }} steps: - name: Checkout (for CLI) - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 with: path: main ref: trunk - name: Checkout (for publishing) - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 with: path: publish # Later, we switch this branch in the script that publishes packages. @@ -326,7 +326,7 @@ jobs: git config user.name "Gutenberg Repository Automation" git config user.email gutenberg@wordpress.org - - name: Setup Node (for CLI) + - name: Setup Node.js uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 with: node-version-file: 'main/.nvmrc' diff --git a/.github/workflows/bundle-size.yml b/.github/workflows/bundle-size.yml index 9ae0116ef1c79b..c4fdafb422c9b6 100644 --- a/.github/workflows/bundle-size.yml +++ b/.github/workflows/bundle-size.yml @@ -37,11 +37,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 with: fetch-depth: 1 - - name: Use desired version of NodeJS + - name: Use desired version of Node.js uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 with: node-version-file: '.nvmrc' diff --git a/.github/workflows/check-components-changelog.yml b/.github/workflows/check-components-changelog.yml new file mode 100644 index 00000000000000..c0cb4894009a2a --- /dev/null +++ b/.github/workflows/check-components-changelog.yml @@ -0,0 +1,65 @@ +name: OPTIONAL - Verify @wordpress/components CHANGELOG update + +on: + pull_request: + types: [opened, synchronize] + paths: + - 'packages/components/**' + - '!packages/components/src/**/stories/**' + - '!packages/components/src/**/test/**' + - '!packages/components/src/**/*.android.js' + - '!packages/components/src/**/*.ios.js' + - '!packages/components/src/**/*.native.js' + - '!packages/components/src/**/*.native.scss' + - '!packages/components/src/**/react-native-*' +jobs: + check: + name: Check CHANGELOG diff + runs-on: ubuntu-latest + steps: + - name: 'Get PR commit count' + run: echo "PR_COMMIT_COUNT=$(( ${{ github.event.pull_request.commits }} + 1 ))" >> "${GITHUB_ENV}" + - name: Checkout code + uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + fetch-depth: ${{ env.PR_COMMIT_COUNT }} + - name: 'Fetch relevant history from origin' + run: git fetch origin ${{ github.event.pull_request.base.ref }} + - name: Check CHANGELOG status + env: + PR_NUMBER: ${{ github.event.number }} + run: | + changelog_path="packages/components/CHANGELOG.md" + optional_check_notice="This isn't a required check, so if you think your changes are small enough that they don't warrant a CHANGELOG entry, please go ahead and merge without one." + + # Fail if the PR doesn't touch the changelog + if git diff --quiet ${{ github.event.pull_request.base.sha }} HEAD -- "$changelog_path"; then + echo "Please add a CHANGELOG entry to $changelog_path" + echo + echo "${optional_check_notice}" + exit 1 + fi + + pr_link_pattern="\[#${PR_NUMBER}\]\(https://github\.com/WordPress/gutenberg/pull/${PR_NUMBER}\)" + pr_link_grep_pattern="\[#${PR_NUMBER}\](https://github\.com/WordPress/gutenberg/pull/${PR_NUMBER})" + + unreleased_section=$(sed -n '/^## Unreleased$/,/^## /p' "${changelog_path}") + + # Confirm that the CHANGELOG has an entry for the current PR + if ! grep -nq -e "${pr_link_grep_pattern}" "${changelog_path}"; then + echo "Please add a CHANGELOG entry to $changelog_path, and make sure your CHANGELOG entry has a link to the current PR." + echo + echo "${optional_check_notice}" + exit 1 + fi + + # Confirm that there is an 'Unreleased' section and that the relevant entry is in that section + if ! grep -nq -e '^## Unreleased' "${changelog_path}" || \ + ! [[ $unreleased_section = *${pr_link_pattern}* ]]; then + echo "Please make sure your CHANGELOG entry is in the \`## Unreleased\` section" + echo + echo "${optional_check_notice}" + exit 1 + fi diff --git a/.github/workflows/create-block.yml b/.github/workflows/create-block.yml index 304052d4297b87..8f76c072133d0c 100644 --- a/.github/workflows/create-block.yml +++ b/.github/workflows/create-block.yml @@ -20,20 +20,17 @@ jobs: strategy: fail-fast: false matrix: - node: [14] + node: ['14'] os: [macos-latest, ubuntu-latest, windows-latest] steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - - name: Use desired version of NodeJS - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 + - name: Setup Node.js and install dependencies + uses: ./.github/setup-node with: node-version: ${{ matrix.node }} - cache: npm - - name: npm install, build, format and lint + - name: Create block shell: bash - run: | - npm ci - bash ./bin/test-create-block.sh + run: bash ./bin/test-create-block.sh diff --git a/.github/workflows/end2end-test.yml b/.github/workflows/end2end-test.yml index 24256e4266a46e..39748288c4d662 100644 --- a/.github/workflows/end2end-test.yml +++ b/.github/workflows/end2end-test.yml @@ -27,18 +27,13 @@ jobs: totalParts: [4] steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - - name: Use desired version of NodeJS - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 - with: - node-version-file: '.nvmrc' - cache: npm + - name: Setup Node.js and install dependencies + uses: ./.github/setup-node - - name: Npm install and build - run: | - npm ci - npm run build + - name: Npm build + run: npm run build - name: Install WordPress run: | @@ -76,18 +71,13 @@ jobs: totalParts: [2] steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - - name: Use desired version of NodeJS - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 - with: - node-version-file: '.nvmrc' - cache: npm + - name: Setup Node.js and install dependencies + uses: ./.github/setup-node - - name: Npm install and build - run: | - npm ci - npm run build + - name: Npm build + run: npm run build - name: Install Playwright dependencies run: | @@ -125,7 +115,7 @@ jobs: steps: # Checkout defaults to using the branch which triggered the event, which # isn't necessarily `trunk` (e.g. in the case of a merge). - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 with: ref: trunk @@ -137,19 +127,14 @@ jobs: name: flaky-tests-report path: flaky-tests - - name: Use desired version of NodeJS + - name: Setup Node.js and install dependencies if: ${{ steps.download_artifact.outcome == 'success' }} - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 - with: - node-version-file: '.nvmrc' - cache: npm + uses: ./.github/setup-node - - name: Npm install and build + - name: Npm build if: ${{ steps.download_artifact.outcome == 'success' }} # TODO: We don't have to build the entire project, just the action itself. - run: | - npm ci - npm run build:packages + run: npm run build:packages - name: Report flaky tests if: ${{ steps.download_artifact.outcome == 'success' }} diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index f56a5ecf48e757..e1da61b72e263b 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -6,5 +6,5 @@ jobs: name: 'Validation' runs-on: ubuntu-latest steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - uses: gradle/wrapper-validation-action@v1 diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index bab6f3d8af5a68..7a5c523c8324b6 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -28,38 +28,19 @@ jobs: name: Run performance tests runs-on: ubuntu-latest if: ${{ github.repository == 'WordPress/gutenberg' }} + env: + WP_ARTIFACTS_PATH: ${{ github.workspace }}/artifacts steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - - name: Use desired version of NodeJS - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 - with: - node-version-file: '.nvmrc' - cache: npm - - - name: Npm install - run: | - npm ci - - - name: Install specific versions of the themes used in tests - run: | - npm run wp-env start - npm run wp-env -- run tests-cli "wp theme update twentytwentyone --version=1.7" - npm run wp-env -- run tests-cli "wp theme update twentytwentythree --version=1.0" - npm run wp-env stop + - name: Setup Node.js and install dependencies + uses: ./.github/setup-node - name: Compare performance with trunk if: github.event_name == 'pull_request' run: ./bin/plugin/cli.js perf $GITHUB_SHA trunk --tests-branch $GITHUB_SHA - - name: Store performance measurements - if: github.event_name == 'pull_request' - uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 - with: - name: perf-test-results - path: ./__test-results/*.json - - name: Compare performance with current WordPress Core and previous Gutenberg versions if: github.event_name == 'release' env: @@ -73,7 +54,7 @@ jobs: WP_VERSION=$(awk -F ': ' '/^Tested up to/{print $2}' readme.txt) IFS=. read -ra WP_VERSION_ARRAY <<< "$WP_VERSION" WP_MAJOR="${WP_VERSION_ARRAY[0]}.${WP_VERSION_ARRAY[1]}" - ./bin/plugin/cli.js perf "wp/$WP_MAJOR" "$PREVIOUS_RELEASE_BRANCH" "$CURRENT_RELEASE_BRANCH" --wp-version "$WP_MAJOR" + ./bin/plugin/cli.js perf "wp/$WP_MAJOR" "$PREVIOUS_RELEASE_BRANCH" "$CURRENT_RELEASE_BRANCH" --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR" - name: Compare performance with base branch if: github.event_name == 'push' @@ -94,27 +75,25 @@ jobs: run: | ./bin/plugin/cli.js perf $(echo $BRANCHES | tr ',' ' ') --tests-branch $GITHUB_SHA --wp-version "$WP_VERSION" - - name: Get commit timestamp - uses: actions/github-script@98814c53be79b1d30f795b907e553d8679345975 # v6.4.0 - if: github.event_name == 'push' - id: commit-timestamp + - name: Archive performance results + if: success() + uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - const commit_details = await github.rest.git.getCommit({owner: context.repo.owner, repo: context.repo.repo, commit_sha: context.sha}); - return parseInt((new Date( commit_details.data.author.date ).getTime() / 1000).toFixed(0)) + name: performance-results + path: ${{ env.WP_ARTIFACTS_PATH }}/*.performance-results.json - name: Publish performance results if: github.event_name == 'push' env: - COMMITTED_AT: ${{ steps.commit-timestamp.outputs.result }} CODEHEALTH_PROJECT_TOKEN: ${{ secrets.CODEHEALTH_PROJECT_TOKEN }} - run: ./bin/log-performance-results.js $CODEHEALTH_PROJECT_TOKEN trunk $GITHUB_SHA debd225d007f4e441ceec80fbd6fa96653f94737 $COMMITTED_AT + run: | + COMMITTED_AT=$(git show -s $GITHUB_SHA --format="%ct") + ./bin/log-performance-results.js $CODEHEALTH_PROJECT_TOKEN trunk $GITHUB_SHA debd225d007f4e441ceec80fbd6fa96653f94737 $COMMITTED_AT - name: Archive debug artifacts (screenshots, HTML snapshots) uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 - if: always() + if: failure() with: name: failures-artifacts - path: ./__test-results/artifacts + path: ${{ env.WP_ARTIFACTS_PATH }} if-no-files-found: ignore diff --git a/.github/workflows/publish-npm-packages.yml b/.github/workflows/publish-npm-packages.yml index 6568d46989fe65..a9b36ba7f98b5c 100644 --- a/.github/workflows/publish-npm-packages.yml +++ b/.github/workflows/publish-npm-packages.yml @@ -30,13 +30,13 @@ jobs: environment: WordPress packages steps: - name: Checkout (for CLI) - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 with: path: main ref: trunk - name: Checkout (for publishing) - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 with: path: publish # Later, we switch this branch in the script that publishes packages. @@ -49,7 +49,7 @@ jobs: git config user.name "Gutenberg Repository Automation" git config user.email gutenberg@wordpress.org - - name: Setup Node (for CLI) + - name: Setup Node.js uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 with: node-version-file: 'main/.nvmrc' diff --git a/.github/workflows/pull-request-automation.yml b/.github/workflows/pull-request-automation.yml index 5163ae3f0ea0cb..56dce7a2825c9b 100644 --- a/.github/workflows/pull-request-automation.yml +++ b/.github/workflows/pull-request-automation.yml @@ -15,17 +15,17 @@ jobs: steps: # Checkout defaults to using the branch which triggered the event, which # isn't necessarily `trunk` (e.g. in the case of a merge). - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 with: ref: trunk - - name: Use desired version of NodeJS + - name: Use desired version of Node.js uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 with: node-version: ${{ matrix.node }} - name: Cache NPM packages - uses: actions/cache@6998d139ddd3e68c71e9e398d8e40b71a2f39812 # v3.2.5 + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 with: # npm cache files are stored in `~/.npm` on Linux/macOS path: ~/.npm diff --git a/.github/workflows/rnmobile-android-runner.yml b/.github/workflows/rnmobile-android-runner.yml index 033789ee30a92d..d04f312b9521bb 100644 --- a/.github/workflows/rnmobile-android-runner.yml +++ b/.github/workflows/rnmobile-android-runner.yml @@ -23,7 +23,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - name: Use desired version of Java uses: actions/setup-java@3f07048e3d294f56e9b90ac5ea2c6f74e9ad0f98 # v3.10.0 @@ -31,19 +31,14 @@ jobs: distribution: 'temurin' java-version: '11' - - name: Use desired version of NodeJS - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 - with: - node-version-file: '.nvmrc' - cache: npm - - - run: npm ci + - name: Setup Node.js and install dependencies + uses: ./.github/setup-node - name: Gradle cache - uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef # v2.3.3 + uses: gradle/gradle-build-action@6095a76664413da4c8c134ee32e8a8ae900f0f1f # v2.4.0 - name: AVD cache - uses: actions/cache@6998d139ddd3e68c71e9e398d8e40b71a2f39812 # v3.2.5 + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 id: avd-cache with: path: | @@ -53,7 +48,7 @@ jobs: - name: Create AVD and generate snapshot for caching if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@50986b1464923454c95e261820bc626f38490ec0 # v2.27.0 + uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b # v2.28.0 with: api-level: ${{ matrix.api-level }} force-avd-creation: false @@ -64,7 +59,7 @@ jobs: script: echo "Generated AVD snapshot for caching." - name: Run tests - uses: reactivecircus/android-emulator-runner@50986b1464923454c95e261820bc626f38490ec0 # v2.27.0 + uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b # v2.28.0 with: api-level: ${{ matrix.api-level }} force-avd-creation: false diff --git a/.github/workflows/rnmobile-ios-runner.yml b/.github/workflows/rnmobile-ios-runner.yml index 52fab007b19173..ca634dc8a9c7a9 100644 --- a/.github/workflows/rnmobile-ios-runner.yml +++ b/.github/workflows/rnmobile-ios-runner.yml @@ -14,30 +14,25 @@ concurrency: jobs: test: - runs-on: macos-11 + runs-on: macos-12 if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} strategy: matrix: - xcode: ['13.2.1'] + xcode: ['13.3.1'] device: ['iPhone 13'] native-test-name: [gutenberg-editor-rendering] steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - - name: Use desired version of NodeJS - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 - with: - node-version-file: '.nvmrc' - cache: npm - - - run: npm ci + - name: Setup Node.js and install dependencies + uses: ./.github/setup-node - name: Prepare build cache key run: find package-lock.json packages/react-native-editor/ios packages/react-native-aztec/ios packages/react-native-bridge/ios -type f -print0 | sort -z | xargs -0 shasum | tee ios-checksums.txt - name: Restore build cache - uses: actions/cache@6998d139ddd3e68c71e9e398d8e40b71a2f39812 # v3.2.5 + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 with: path: | packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app @@ -45,7 +40,7 @@ jobs: key: ${{ runner.os }}-ios-build-${{ matrix.xcode }}-${{ matrix.device }}-${{ hashFiles('ios-checksums.txt') }} - name: Restore pods cache - uses: actions/cache@6998d139ddd3e68c71e9e398d8e40b71a2f39812 # v3.2.5 + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 with: path: | packages/react-native-editor/ios/Pods @@ -66,8 +61,8 @@ jobs: - name: Build Web Driver Agent (if needed) run: test -d packages/react-native-editor/ios/build/WDA || npm run native test:e2e:build-wda - - name: Force update Launch Database to prevent issues when opening the Simulator app - run: /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -f /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer/Applications/Simulator.app + - name: Launch simulator + run: open -a Simulator && xcrun simctl boot '${{ matrix.device }}' - name: Run iOS Device Tests run: TEST_RN_PLATFORM=ios npm run native device-tests:local ${{ matrix.native-test-name }} diff --git a/.github/workflows/stale-issue-gardening.yml b/.github/workflows/stale-issue-gardening.yml index 11134fe68062a6..078108a182850e 100644 --- a/.github/workflows/stale-issue-gardening.yml +++ b/.github/workflows/stale-issue-gardening.yml @@ -42,7 +42,7 @@ jobs: steps: - name: Update issues - uses: actions/stale@6f05e4244c9a0b2ed3401882b05d701dd0a7289b # v7.0.0 + uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: ${{ matrix.message }} diff --git a/.github/workflows/static-checks.yml b/.github/workflows/static-checks.yml index 00002aaf52f191..3b457401560a8d 100644 --- a/.github/workflows/static-checks.yml +++ b/.github/workflows/static-checks.yml @@ -22,9 +22,9 @@ jobs: if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - - name: Use desired version of NodeJS + - name: Use desired version of Node.js uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 with: node-version-file: '.nvmrc' diff --git a/.github/workflows/storybook-pages.yml b/.github/workflows/storybook-pages.yml index 616fad4e55b5aa..e21c1f14d8be21 100644 --- a/.github/workflows/storybook-pages.yml +++ b/.github/workflows/storybook-pages.yml @@ -12,18 +12,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 with: ref: trunk - - name: Use desired version of NodeJS - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 - with: - node-version-file: '.nvmrc' - cache: npm - - - name: Install Dependencies - run: npm ci + - name: Setup Node.js and install dependencies + uses: ./.github/setup-node - name: Build Storybook run: npm run storybook:build diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 6552f2ecf6a2b0..16a7eba93858cc 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -31,21 +31,18 @@ jobs: node: ['14'] steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - - name: Use desired version of NodeJS - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 + - name: Setup Node.js and install dependencies + uses: ./.github/setup-node with: node-version: ${{ matrix.node }} - cache: npm - - name: Npm install and build + - name: Npm build # It's not necessary to run the full build, since Jest can interpret # source files with `babel-jest`. Some packages have their own custom # build tasks, however. These must be run. - run: | - npm ci - npx lerna run build + run: npx lerna run build - name: Running the tests run: npm run test:unit -- --ci --maxWorkers=2 --cacheDirectory="$HOME/.jest-cache" @@ -53,8 +50,34 @@ jobs: - name: Running the date tests run: npm run test:unit:date -- --ci --maxWorkers=2 --cacheDirectory="$HOME/.jest-cache" + compute-previous-wordpress-version: + name: Compute previous WordPress version + runs-on: ubuntu-latest + outputs: + previous-wordpress-version: ${{ steps.get-previous-wordpress-version.outputs.previous-wordpress-version }} + + steps: + - name: Get previous WordPress version + id: get-previous-wordpress-version + run: | + curl \ + -H "Accept: application/json" \ + -o versions.json \ + "http://api.wordpress.org/core/stable-check/1.0/" + LATEST_WP_VERSION=$(jq --raw-output 'with_entries(select(.value=="latest"))|keys[]' versions.json) + IFS='.' read LATEST_WP_MAJOR LATEST_WP_MINOR LATEST_WP_PATCH <<< "${LATEST_WP_VERSION}" + if [[ ${LATEST_WP_MINOR} == "0" ]]; then + PREVIOUS_WP_SERIES="$((LATEST_WP_MAJOR - 1)).9" + else + PREVIOUS_WP_SERIES="${LATEST_WP_MAJOR}.$((LATEST_WP_MINOR - 1))" + fi + PREVIOUS_WP_VERSION=$(jq --raw-output --arg series "${PREVIOUS_WP_SERIES}" 'with_entries(select(.key|startswith($series)))|keys[-1]' versions.json) + echo "previous-wordpress-version=${PREVIOUS_WP_VERSION}" >> $GITHUB_OUTPUT + rm versions.json + test-php: - name: PHP ${{ matrix.php }}${{ matrix.multisite && ' multisite' || '' }} on ubuntu-latest + name: PHP ${{ matrix.php }}${{ matrix.multisite && ' multisite' || '' }}${{ matrix.wordpress != '' && format( ' (WP {0}) ', matrix.wordpress ) || '' }} on ubuntu-latest + needs: compute-previous-wordpress-version runs-on: ubuntu-latest timeout-minutes: 20 if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} @@ -72,18 +95,25 @@ jobs: - '8.1' - '8.2' multisite: [false, true] + wordpress: [''] # Latest WordPress version. + include: + # Test with the previous WP version. + - php: '5.6' + wordpress: ${{ needs.compute-previous-wordpress-version.outputs.previous-wordpress-version }} + - php: '7.4' + wordpress: ${{ needs.compute-previous-wordpress-version.outputs.previous-wordpress-version }} + - php: '8.2' + wordpress: ${{ needs.compute-previous-wordpress-version.outputs.previous-wordpress-version }} env: WP_ENV_PHP_VERSION: ${{ matrix.php }} + WP_ENV_CORE: ${{ matrix.wordpress == '' && 'WordPress/WordPress' || format( 'https://wordpress.org/wordpress-{0}.zip', matrix.wordpress ) }} steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - - name: Set up Node.js - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 - with: - node-version-file: '.nvmrc' - cache: npm + - name: Setup Node.js and install dependencies + uses: ./.github/setup-node ## # This allows Composer dependencies to be installed using a single step. @@ -116,10 +146,8 @@ jobs: with: custom-cache-suffix: $(/bin/date -u --date='last Mon' "+%F") - - name: Install npm dependencies - run: | - npm ci - npm run build + - name: Npm build + run: npm run build - name: Docker debug information run: | @@ -152,11 +180,35 @@ jobs: - name: Running single site unit tests if: ${{ ! matrix.multisite }} - run: npm run test:unit:php + run: | + set -o pipefail + npm run test:unit:php | tee phpunit.log - name: Running multisite unit tests if: ${{ matrix.multisite }} - run: npm run test:unit:php:multisite + run: | + set -o pipefail + npm run test:unit:php:multisite | tee phpunit.log + + # Verifies that PHPUnit actually runs in the first place. We want visibility + # into issues which can cause it to fail silently, so we check the output + # to verify that at least 500 tests have passed. This is an arbitrary + # number, but makes sure a drastic change doesn't happen without us noticing. + - name: Check number of passed tests + run: | + # Note: relies on PHPUnit execution to fail on test failure. + # Extract the number of executed tests from the log file. + if ! num_tests=$(grep -Eo 'OK \([0-9]+ tests' phpunit.log) ; then + if ! num_tests=$(grep -Eo 'Tests: [0-9]+, Assertions:' phpunit.log) ; then + echo "PHPUnit failed or did not run. Check the PHPUnit output in the previous step to debug." && exit 1 + fi + fi + # Extract just the number of tests from the string. + num_tests=$(echo "$num_tests" | grep -Eo '[0-9]+') + if [ $num_tests -lt 500 ] ; then + echo "Only $num_tests tests passed, which is much fewer than expected." && exit 1 + fi + echo "$num_tests tests passed." phpcs: name: PHP coding standards @@ -166,7 +218,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - name: Set up PHP uses: shivammathur/setup-php@d30ad8b1843ace22e6698ab99bbafaa747b6bd0d # v2.24.0 @@ -182,7 +234,7 @@ jobs: run: echo "date=$(/bin/date -u --date='last Mon' "+%F")" >> $GITHUB_OUTPUT - name: Cache PHPCS scan cache - uses: actions/cache@6998d139ddd3e68c71e9e398d8e40b71a2f39812 # v3.0.11 + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 with: path: .cache/phpcs.json key: ${{ runner.os }}-date-${{ steps.get-date.outputs.date }}-phpcs-cache-${{ hashFiles('**/composer.json', 'phpcs.xml.dist') }} @@ -233,21 +285,16 @@ jobs: if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - - name: Use desired version of NodeJS - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 - with: - node-version-file: '.nvmrc' - cache: npm + - name: Setup Node.js and install dependencies + uses: ./.github/setup-node - - name: Npm install and build + - name: Npm build # It's not necessary to run the full build, since Jest can interpret # source files with `babel-jest`. Some packages have their own custom # build tasks, however. These must be run. - run: | - npm ci - npx lerna run build + run: npx lerna run build - name: Running the tests run: npm run native test -- --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 5cb432ee837ee3..392cf912e8db54 100644 --- a/.github/workflows/upload-release-to-plugin-repo.yml +++ b/.github/workflows/upload-release-to-plugin-repo.yml @@ -39,7 +39,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 with: ref: ${{ matrix.branch }} token: ${{ secrets.GUTENBERG_TOKEN }} diff --git a/.gitignore b/.gitignore index ee043c9a3e4b21..4a7f4708ce399a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ gutenberg.zip coverage *-performance-results.json .phpunit.result.cache +.reassure + # Directories/files that may appear in your environment *.log @@ -36,6 +38,7 @@ test/native/junit.xml # Local overrides .wp-env.override.json +playwright.config.override.ts phpcs.xml phpunit.xml phpunit-watcher.yml diff --git a/README.md b/README.md index 126368cb207745..8ac17b8574c52f 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Get hands on: check out the [block editor live demo](https://wordpress.org/guten - **Download:** To use the latest release of the Gutenberg plugin on your WordPress site: install from the plugins page in wp-admin, or [download from the WordPress.org plugins repository](https://wordpress.org/plugins/gutenberg/). -- **User Documentation:** See the [WordPress Editor documentation](https://wordpress.org/support/article/wordpress-editor/) for detailed docs on using the editor as an author creating posts and pages. +- **User Documentation:** See the [WordPress Editor documentation](https://wordpress.org/documentation/article/wordpress-block-editor/) for detailed docs on using the editor as an author creating posts and pages. - **User Support:** If you have run into an issue, you should check the [Support Forums first](https://wordpress.org/support/forums/). The forums are a great place to get help. If you have a bug to report, please [submit it to the Gutenberg repository](https://github.com/wordpress/gutenberg/issues). Please search prior to creating a new bug to confirm its not a duplicate. @@ -43,7 +43,7 @@ Review the [Create a Block tutorial](/docs/getting-started/create-block/README.m Gutenberg is an open-source project and welcomes all contributors from code to design, and from documentation to triage. The project is built by many contributors and volunteers and we'd love your help building it. -See the [Contributors Handbook](https://developer.wordpress.org/block-editor/contributors/) for all the details on how you can contribute. +See the [Contributors Handbook](https://developer.wordpress.org/block-editor/contributors/) for all the details on how you can contribute. To get up and running quickly with **code contribution** see [Getting Started With Code Contribution](/docs/contributors/code/getting-started-with-code-contribution.md). Also check out the other resources available on the [Code Contributions](/docs/contributors/code/README.md) page. diff --git a/bin/api-docs/gen-theme-reference.js b/bin/api-docs/gen-theme-reference.js index 45edecf6bd1a2f..f638bb708890a8 100644 --- a/bin/api-docs/gen-theme-reference.js +++ b/bin/api-docs/gen-theme-reference.js @@ -211,6 +211,35 @@ styleSections.forEach( ( section ) => { autogen += getSectionMarkup( section, styles[ section ], 'styles' ); } ); +const templateTableGeneration = ( themeJson, context ) => { + let content = ''; + content += '## ' + context + '\n\n'; + content += themeJson.properties[ context ].description + '\n\n'; + content += + 'Type: `' + themeJson.properties[ context ].items.type + '`.\n\n'; + content += '| Property | Description | Type |\n'; + content += '| --- | --- | --- |\n'; + keys( themeJson.properties[ context ].items.properties ).forEach( + ( key ) => { + content += `| ${ key } | ${ themeJson.properties[ context ].items.properties[ key ].description } | ${ themeJson.properties[ context ].items.properties[ key ].type } |\n`; + } + ); + content += '\n\n'; + + return content; +}; + +// customTemplates +autogen += templateTableGeneration( themejson, 'customTemplates' ); + +// templateParts +autogen += templateTableGeneration( themejson, 'templateParts' ); + +// Patterns +autogen += '## Patterns' + '\n\n'; +autogen += themejson.properties.patterns.description + '\n'; +autogen += 'Type: `' + themejson.properties.patterns.type + '`.\n\n'; + // Read existing file to wrap auto generated content. let docsContent = fs.readFileSync( THEME_JSON_REF_DOC, { encoding: 'utf8', diff --git a/bin/build-plugin-zip.sh b/bin/build-plugin-zip.sh index 4ba931c4a4aeb6..131e434d1383d0 100755 --- a/bin/build-plugin-zip.sh +++ b/bin/build-plugin-zip.sh @@ -83,6 +83,7 @@ build_files=$( build/block-library/blocks/*.php \ build/block-library/blocks/*/block.json \ build/block-library/blocks/*/*.{js,js.map,css,asset.php} \ + build/block-library/interactivity/*.{js,js.map,asset.php} \ build/edit-widgets/blocks/*/block.json \ build/widgets/blocks/*.php \ build/widgets/blocks/*/block.json \ diff --git a/bin/log-performance-results.js b/bin/log-performance-results.js index eac2f13d0ac977..9bc7ef9cb99b11 100755 --- a/bin/log-performance-results.js +++ b/bin/log-performance-results.js @@ -10,22 +10,25 @@ const [ token, branch, hash, baseHash, timestamp ] = process.argv.slice( 2 ); const resultsFiles = [ { - file: 'post-editor-performance-results.json', + file: 'post-editor.performance-results.json', metricsPrefix: '', }, { - file: 'front-end-block-theme-performance-results.json', + file: 'front-end-block-theme.performance-results.json', metricsPrefix: 'block-theme-', }, { - file: 'front-end-classic-theme-performance-results.json', + file: 'front-end-classic-theme.performance-results.json', metricsPrefix: 'classic-theme-', }, ]; const performanceResults = resultsFiles.map( ( { file } ) => JSON.parse( - fs.readFileSync( path.join( __dirname, '../' + file ), 'utf8' ) + fs.readFileSync( + path.join( process.env.WP_ARTIFACTS_PATH, file ), + 'utf8' + ) ) ); diff --git a/bin/packages/build-worker.js b/bin/packages/build-worker.js index e82091ff843749..3f1512ef0feb78 100644 --- a/bin/packages/build-worker.js +++ b/bin/packages/build-worker.js @@ -103,9 +103,13 @@ async function buildCSS( file ) { 'animations', 'z-index', ] - // Editor styles should be excluded from the default CSS vars output. + // Editor and component styles should be excluded from the default CSS vars output. .concat( - file.includes( 'common.scss' ) || ! file.includes( 'block-library' ) + file.includes( 'common.scss' ) || + ! ( + file.includes( 'block-library' ) || + file.includes( 'components' ) + ) ? [ 'default-custom-properties' ] : [] ) diff --git a/bin/packages/check-build-type-declaration-files.js b/bin/packages/check-build-type-declaration-files.js new file mode 100644 index 00000000000000..cd8bad2a70b530 --- /dev/null +++ b/bin/packages/check-build-type-declaration-files.js @@ -0,0 +1,121 @@ +/** + * This script verifies the published index.d.ts file for every package which both + * builds types and also sets checkJs to false in its tsconfig.json. (This scenario + * can cause unchecked errors in JS files to be included in the compiled types.) + * + * We do so by running `tsc --noEmit` on the $package/build-types/index.d.ts file. + * This also verifies everything index.d.ts references, so it checks the entire + * public api of the type declarations for that package. + * + * @see https://github.com/WordPress/gutenberg/pull/49650 for more discussion. + */ + +/** + * External dependencies + */ +const fs = require( 'fs' ).promises; +const path = require( 'path' ); +const { exec } = require( 'child_process' ); +const chalk = require( 'chalk' ); + +/** + * Returns whether a package needs its compiled types to be double-checked. This + * needs to happen when both of these are true: + * 1. The package compiles types. (It has a tsconfig file.) + * 2. The tsconfig sets checkJs to false. + * + * NOTE: In the future, if we run into issues parsing JSON, we should migrate to + * a proper json5 parser, such as the json5 npm package. The current regex just + * handles comments, which at the time is the only thing we use from JSON5. + * + * @param {string} packagePath Path to the package. + * @return {boolean} whether or not the package checksJs. + */ +async function packageNeedsExtraCheck( packagePath ) { + const configPath = path.join( packagePath, 'tsconfig.json' ); + + try { + const tsconfigRaw = await fs.readFile( configPath, 'utf-8' ); + // Removes comments from the JSON5 string to convert it to plain JSON. + const jsonString = tsconfigRaw.replace( /\s+\/\/.*$/gm, '' ); + const config = JSON.parse( jsonString ); + + // If checkJs both exists and is false, then we need the extra check. + return config.compilerOptions?.checkJs === false; + } catch ( e ) { + if ( e.code !== 'ENOENT' ) { + throw e; + } + + // No tsconfig means no checkJs + return false; + } +} + +// Returns the path to the build-types declaration file for a package if it exists. +// Throws an error and exits the script otherwise. +async function getDecFile( packagePath ) { + const decFile = path.join( packagePath, 'build-types', 'index.d.ts' ); + try { + await fs.access( decFile ); + return decFile; + } catch ( err ) { + console.error( + `Cannot access this declaration file. You may need to run tsc again: ${ decFile }` + ); + process.exit( 1 ); + } +} + +async function typecheckDeclarations( file ) { + return new Promise( ( resolve, reject ) => { + exec( `npx tsc --noEmit ${ file }`, ( error, stdout, stderr ) => { + if ( error ) { + reject( { file, error, stderr, stdout } ); + } else { + resolve( { file, stdout } ); + } + } ); + } ); +} + +async function checkUnverifiedDeclarationFiles() { + const packageDir = path.resolve( 'packages' ); + const packageDirs = ( + await fs.readdir( packageDir, { withFileTypes: true } ) + ) + .filter( ( dirent ) => dirent.isDirectory() ) + .map( ( dirent ) => path.join( packageDir, dirent.name ) ); + + // Finds the compiled type declarations for each package which both checks + // types and has checkJs disabled. + const declarations = ( + await Promise.all( + packageDirs.map( async ( pkg ) => + ( await packageNeedsExtraCheck( pkg ) ) + ? getDecFile( pkg ) + : null + ) + ) + ).filter( Boolean ); + + const tscResults = await Promise.allSettled( + declarations.map( typecheckDeclarations ) + ); + + tscResults.forEach( ( { status, reason } ) => { + if ( status !== 'fulfilled' ) { + console.error( + chalk.red( + `Incorrect published types for ${ reason.file }:\n` + ), + reason.stdout + ); + } + } ); + + if ( tscResults.some( ( { status } ) => status !== 'fulfilled' ) ) { + process.exit( 1 ); + } +} +checkUnverifiedDeclarationFiles(); diff --git a/bin/plugin/commands/changelog.js b/bin/plugin/commands/changelog.js index e6fb8c7054b16e..69be77ac669911 100644 --- a/bin/plugin/commands/changelog.js +++ b/bin/plugin/commands/changelog.js @@ -1,7 +1,6 @@ /** * External dependencies */ -const { groupBy } = require( 'lodash' ); const Octokit = require( '@octokit/rest' ); const { sprintf } = require( 'sprintf-js' ); const semver = require( 'semver' ); @@ -62,8 +61,7 @@ const UNKNOWN_FEATURE_FALLBACK_NAME = 'Uncategorized'; * @type {Record} */ const LABEL_TYPE_MAPPING = { - '[Feature] Navigation Screen': 'Experiments', - '[Package] Dependency Extraction Webpack Plugin': 'Tools', + '[Type] Developer Documentation': 'Documentation', '[Package] Jest Puppeteer aXe': 'Tools', '[Package] E2E Tests': 'Tools', '[Package] E2E Test Utils': 'Tools', @@ -75,16 +73,17 @@ const LABEL_TYPE_MAPPING = { '[Package] Scripts': 'Tools', '[Type] Build Tooling': 'Tools', 'Automated Testing': 'Tools', + '[Package] Dependency Extraction Webpack Plugin': 'Tools', + '[Type] Code Quality': 'Code Quality', + '[Type] Performance': 'Performance', + '[Type] Security': 'Security', + '[Feature] Navigation Screen': 'Experiments', '[Type] Experimental': 'Experiments', '[Type] Bug': 'Bug Fixes', '[Type] Regression': 'Bug Fixes', - '[Type] Feature': 'Features', '[Type] Enhancement': 'Enhancements', '[Type] New API': 'New APIs', - '[Type] Performance': 'Performance', - '[Type] Developer Documentation': 'Documentation', - '[Type] Code Quality': 'Code Quality', - '[Type] Security': 'Security', + '[Type] Feature': 'Features', }; /** @@ -304,12 +303,6 @@ function getIssueType( issue ) { ...getTypesByTitle( issue.title ), ]; - // Force all tasks identified as Documentation tasks - // to appear under the main "Documentation" section. - if ( candidates.includes( 'Documentation' ) ) { - return 'Documentation'; - } - return candidates.length ? candidates.sort( sortType )[ 0 ] : 'Various'; } @@ -378,7 +371,7 @@ function getIssueFeature( issue ) { */ function sortType( a, b ) { const [ aIndex, bIndex ] = [ a, b ].map( ( title ) => { - return Object.keys( LABEL_TYPE_MAPPING ).indexOf( title ); + return Object.values( LABEL_TYPE_MAPPING ).indexOf( title ); } ); return aIndex - bIndex; @@ -711,9 +704,19 @@ async function fetchAllPullRequests( octokit, settings ) { function getChangelog( pullRequests ) { let changelog = '## Changelog\n\n'; - const groupedPullRequests = groupBy( - skipCreatedByBots( pullRequests ), - getIssueType + const groupedPullRequests = skipCreatedByBots( pullRequests ).reduce( + ( + /** @type {Record} */ acc, + pr + ) => { + const issueType = getIssueType( pr ); + if ( ! acc[ issueType ] ) { + acc[ issueType ] = []; + } + acc[ issueType ].push( pr ); + return acc; + }, + {} ); const sortedGroups = Object.keys( groupedPullRequests ).sort( sortGroup ); @@ -732,7 +735,20 @@ function getChangelog( pullRequests ) { changelog += '### ' + group + '\n\n'; // Group PRs within this section into "Features". - const featureGroups = groupBy( groupPullRequests, getIssueFeature ); + const featureGroups = groupPullRequests.reduce( + ( + /** @type {Record} */ acc, + pr + ) => { + const issueFeature = getIssueFeature( pr ); + if ( ! acc[ issueFeature ] ) { + acc[ issueFeature ] = []; + } + acc[ issueFeature ].push( pr ); + return acc; + }, + {} + ); const featuredGroupNames = sortFeatureGroups( featureGroups ); @@ -903,6 +919,10 @@ function getContributorProps( pullRequests ) { getContributorPropsMarkdownList, ] )( pullRequests ); + if ( ! contributorsList ) { + return ''; + } + return ( '## First time contributors' + '\n\n' + diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index 75ab95a4cc4a10..57c6674500a221 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -3,7 +3,6 @@ */ const fs = require( 'fs' ); const path = require( 'path' ); -const { mapValues, kebabCase } = require( 'lodash' ); const SimpleGit = require( 'simple-git' ); /** @@ -18,6 +17,9 @@ const { } = require( '../lib/utils' ); const config = require( '../config' ); +const ARTIFACTS_PATH = + process.env.WP_ARTIFACTS_PATH || path.join( process.cwd(), 'artifacts' ); + /** * @typedef WPPerformanceCommandOptions * @@ -83,6 +85,17 @@ const config = require( '../config' ); * @property {number=} maxListViewOpen Max time to open list view. */ +/** + * Sanitizes branch name to be used in a path or a filename. + * + * @param {string} branch + * + * @return {string} Sanitized branch name. + */ +function sanitizeBranchName( branch ) { + return branch.replace( /[^a-zA-Z0-9-]/g, '-' ); +} + /** * Computes the average number from an array numbers. * @@ -182,38 +195,22 @@ function curateResults( testSuite, results ) { * @return {Promise} Performance results for the branch. */ async function runTestSuite( testSuite, performanceTestDirectory, runKey ) { - try { - await runShellScript( - `npm run test:performance -- packages/e2e-tests/specs/performance/${ testSuite }.test.js`, - performanceTestDirectory - ); - } catch ( error ) { - fs.mkdirSync( './__test-results/artifacts', { recursive: true } ); - const artifactsFolder = path.join( - performanceTestDirectory, - 'artifacts/' - ); - await runShellScript( - 'cp -Rv ' + artifactsFolder + ' ' + './__test-results/artifacts/' - ); - - throw error; - } + const resultsFilename = `${ runKey }.performance-results.json`; - const resultsFile = path.join( + await runShellScript( + `npm run test:performance -- ${ testSuite }`, performanceTestDirectory, - `packages/e2e-tests/specs/performance/${ testSuite }.test.results.json` - ); - fs.mkdirSync( './__test-results', { recursive: true } ); - fs.copyFileSync( resultsFile, `./__test-results/${ runKey }.results.json` ); - const rawResults = await readJSONFile( - path.join( - performanceTestDirectory, - `packages/e2e-tests/specs/performance/${ testSuite }.test.results.json` - ) + { + ...process.env, + WP_ARTIFACTS_PATH: ARTIFACTS_PATH, + RESULTS_FILENAME: resultsFilename, + } ); - return curateResults( testSuite, rawResults ); + return curateResults( + testSuite, + await readJSONFile( path.join( ARTIFACTS_PATH, resultsFilename ) ) + ); } /** @@ -299,8 +296,8 @@ async function runPerformanceTests( branches, options ) { const branchDirectories = {}; for ( const branch of branches ) { log( ` >> Branch: ${ branch }` ); - const environmentDirectory = - rootDirectory + '/envs/' + kebabCase( branch ); + const sanitizedBranch = sanitizeBranchName( branch ); + const environmentDirectory = rootDirectory + '/envs/' + sanitizedBranch; // @ts-ignore branchDirectories[ branch ] = environmentDirectory; const buildPath = `${ environmentDirectory }/plugin`; @@ -328,15 +325,41 @@ async function runPerformanceTests( branches, options ) { buildPath ); - await runShellScript( - 'cp ' + - path.resolve( - performanceTestDirectory, - 'bin/plugin/utils/.wp-env.performance.json' - ) + - ' ' + - environmentDirectory + - '/.wp-env.json' + // Create the config file for the current env. + fs.writeFileSync( + path.join( environmentDirectory, '.wp-env.json' ), + JSON.stringify( + { + core: 'WordPress/WordPress', + plugins: [ path.join( environmentDirectory, 'plugin' ) ], + themes: [ + path.join( + performanceTestDirectory, + 'test/emptytheme' + ), + 'https://downloads.wordpress.org/theme/twentytwentyone.1.7.zip', + 'https://downloads.wordpress.org/theme/twentytwentythree.1.0.zip', + ], + env: { + tests: { + mappings: { + 'wp-content/mu-plugins': path.join( + performanceTestDirectory, + 'packages/e2e-tests/mu-plugins' + ), + 'wp-content/plugins/gutenberg-test-plugins': + path.join( + performanceTestDirectory, + 'packages/e2e-tests/plugins' + ), + }, + }, + }, + }, + null, + 2 + ), + 'utf8' ); if ( options.wpVersion ) { @@ -393,32 +416,39 @@ async function runPerformanceTests( branches, options ) { /** @type {Record>} */ const results = {}; + const wpEnvPath = path.join( + performanceTestDirectory, + 'node_modules/.bin/wp-env' + ); + for ( const testSuite of testSuites ) { results[ testSuite ] = {}; /** @type {Array>} */ const rawResults = []; - // Alternate three times between branches. for ( let i = 0; i < TEST_ROUNDS; i++ ) { + const roundInfo = `round ${ i + 1 } of ${ TEST_ROUNDS }`; + log( ` >> Suite: ${ testSuite } (${ roundInfo })` ); rawResults[ i ] = {}; for ( const branch of branches ) { - const runKey = `${ branch }_${ testSuite }_run-${ i }`; + const sanitizedBranch = sanitizeBranchName( branch ); + const runKey = `${ testSuite }_${ sanitizedBranch }_run-${ i }`; // @ts-ignore const environmentDirectory = branchDirectories[ branch ]; - log( ` >> Branch: ${ branch }, Suite: ${ testSuite }` ); - log( ' >> Starting the environment.' ); + log( ` >> Branch: ${ branch }` ); + log( ' >> Starting the environment.' ); await runShellScript( - '../../tests/node_modules/.bin/wp-env start', + `${ wpEnvPath } start`, environmentDirectory ); - log( ' >> Running the test.' ); + log( ' >> Running the test.' ); rawResults[ i ][ branch ] = await runTestSuite( testSuite, performanceTestDirectory, runKey ); - log( ' >> Stopping the environment' ); + log( ' >> Stopping the environment' ); await runShellScript( - '../../tests/node_modules/.bin/wp-env stop', + `${ wpEnvPath } stop`, environmentDirectory ); } @@ -444,10 +474,26 @@ async function runPerformanceTests( branches, options ) { ( r ) => r[ branch ][ dataPoint ] ); } ); - const medians = mapValues( resultsByDataPoint, median ); + // @ts-ignore + const medians = Object.fromEntries( + Object.entries( resultsByDataPoint ).map( + ( [ dataPoint, dataPointResults ] ) => [ + dataPoint, + median( dataPointResults ), + ] + ) + ); // Format results as times. - results[ testSuite ][ branch ] = mapValues( medians, formatTime ); + // @ts-ignore + results[ testSuite ][ branch ] = Object.fromEntries( + Object.entries( medians ).map( + ( [ dataPoint, dataPointMedian ] ) => [ + dataPoint, + formatTime( dataPointMedian ), + ] + ) + ); } } @@ -481,9 +527,9 @@ async function runPerformanceTests( branches, options ) { ); console.table( invertedResult ); - const resultsFilename = testSuite + '-performance-results.json'; + const resultsFilename = testSuite + '.performance-results.json'; fs.writeFileSync( - path.resolve( __dirname, '../../../', resultsFilename ), + path.join( ARTIFACTS_PATH, resultsFilename ), JSON.stringify( results[ testSuite ], null, 2 ) ); } diff --git a/bin/plugin/commands/test/__snapshots__/changelog.js.snap b/bin/plugin/commands/test/__snapshots__/changelog.js.snap index df71684c9aa71d..2e79ecda56a7c0 100644 --- a/bin/plugin/commands/test/__snapshots__/changelog.js.snap +++ b/bin/plugin/commands/test/__snapshots__/changelog.js.snap @@ -5,12 +5,8 @@ exports[`getChangelog verify that the changelog is properly formatted 1`] = ` ### Enhancements -- Scripts: Use cssnano to minimize CSS files with build. ([33750](https://github.com/WordPress/gutenberg/pull/33750)) -- Scripts: Webpack configuration update to minimize CSS. ([33676](https://github.com/WordPress/gutenberg/pull/33676)) - #### Components - Add new ColorPicker. ([33714](https://github.com/WordPress/gutenberg/pull/33714)) -- Promote \`ItemGroup\`. ([33701](https://github.com/WordPress/gutenberg/pull/33701)) - Update snackbar to use framer motion instead of react spring. ([33717](https://github.com/WordPress/gutenberg/pull/33717)) - Use updated range styles. ([33824](https://github.com/WordPress/gutenberg/pull/33824)) @@ -39,7 +35,6 @@ exports[`getChangelog verify that the changelog is properly formatted 1`] = ` ### Bug Fixes - Correct \`function_exists()\` check typo introduced in #33331. ([33513](https://github.com/WordPress/gutenberg/pull/33513)) -- ESLint Plugin: Include .jsx extenstion when linting import statements. ([33746](https://github.com/WordPress/gutenberg/pull/33746)) - Fix block appender position in classic themes. ([33895](https://github.com/WordPress/gutenberg/pull/33895)) - Fix misspelling of "queries" in filter documentation. ([33799](https://github.com/WordPress/gutenberg/pull/33799)) - Fix positioning discrepancy with draggable chip. ([33893](https://github.com/WordPress/gutenberg/pull/33893)) @@ -81,12 +76,6 @@ exports[`getChangelog verify that the changelog is properly formatted 1`] = ` #### Meta Boxes - Fix Safari 13 metaboxes from overlapping the content. ([33817](https://github.com/WordPress/gutenberg/pull/33817)) -#### Build Tooling -- Readable JS assets Plugin: Fix webpack 5 support. ([33785](https://github.com/WordPress/gutenberg/pull/33785)) - -#### Navigation Screen -- Fix regressed menu selection dropdown placeholder value for Nav Editor menu locations UI. ([33748](https://github.com/WordPress/gutenberg/pull/33748)) - #### Accessibility - Fix some JAWS bugs. ([33627](https://github.com/WordPress/gutenberg/pull/33627)) @@ -116,6 +105,15 @@ exports[`getChangelog verify that the changelog is properly formatted 1`] = ` - Refactor the HierarchicalTermSelector so that it does not cause unnecessary loading of terms. ([33418](https://github.com/WordPress/gutenberg/pull/33418)) +### Experiments + +#### Navigation Screen +- Fix regressed menu selection dropdown placeholder value for Nav Editor menu locations UI. ([33748](https://github.com/WordPress/gutenberg/pull/33748)) + +#### Components +- Promote \`ItemGroup\`. ([33701](https://github.com/WordPress/gutenberg/pull/33701)) + + ### Documentation - Add documentation to disable remote calls for block patterns. ([33930](https://github.com/WordPress/gutenberg/pull/33930)) @@ -133,8 +131,6 @@ exports[`getChangelog verify that the changelog is properly formatted 1`] = ` ### Code Quality -- Scripts: Fix typo in format change message. ([33945](https://github.com/WordPress/gutenberg/pull/33945)) - #### Components - Components utils: \`rtl()\` return type, \`rtl.watch()\` utility. ([33882](https://github.com/WordPress/gutenberg/pull/33882)) - InputControl to TypeScript. ([33696](https://github.com/WordPress/gutenberg/pull/33696)) @@ -157,17 +153,22 @@ exports[`getChangelog verify that the changelog is properly formatted 1`] = ` ### Tools +- ESLint Plugin: Include .jsx extenstion when linting import statements. ([33746](https://github.com/WordPress/gutenberg/pull/33746)) - GitHub Templates: Fix spacing in bug report template. ([33761](https://github.com/WordPress/gutenberg/pull/33761)) - GitHub Templates: Format bug report template. ([33786](https://github.com/WordPress/gutenberg/pull/33786)) +- Scripts: Fix typo in format change message. ([33945](https://github.com/WordPress/gutenberg/pull/33945)) +- Scripts: Use cssnano to minimize CSS files with build. ([33750](https://github.com/WordPress/gutenberg/pull/33750)) +- Scripts: Webpack configuration update to minimize CSS. ([33676](https://github.com/WordPress/gutenberg/pull/33676)) - Update bug issue template to use forms. ([33713](https://github.com/WordPress/gutenberg/pull/33713)) +#### Build Tooling +- Readable JS assets Plugin: Fix webpack 5 support. ([33785](https://github.com/WordPress/gutenberg/pull/33785)) +- Scripts: Update webpack to v5 (try 2). ([33818](https://github.com/WordPress/gutenberg/pull/33818)) + #### Testing - Add search performance measure and make other measures more stable. ([33848](https://github.com/WordPress/gutenberg/pull/33848)) - E2E: Block Hierarchy Navigation wait for the column to be highlighted. ([33721](https://github.com/WordPress/gutenberg/pull/33721)) -#### Build Tooling -- Scripts: Update webpack to v5 (try 2). ([33818](https://github.com/WordPress/gutenberg/pull/33818)) - ### Various diff --git a/bin/plugin/commands/test/changelog.js b/bin/plugin/commands/test/changelog.js index e9e1872c38c997..52a9391af11d13 100644 --- a/bin/plugin/commands/test/changelog.js +++ b/bin/plugin/commands/test/changelog.js @@ -188,6 +188,17 @@ describe( 'getIssueType', () => { expect( result ).toBe( 'Enhancements' ); } ); + + it( 'prioritizes meta categories', () => { + const result = getIssueType( { + labels: [ + { name: '[Type] Bug' }, + { name: '[Type] Build Tooling' }, + ], + } ); + + expect( result ).toBe( 'Tools' ); + } ); } ); describe( 'getIssueFeature', () => { @@ -474,6 +485,11 @@ describe( 'getContributorProps', () => { // npm run other:changelog -- --milestone="Gutenberg 11.3" expect( getContributorProps( pullRequests ) ).toMatchSnapshot(); } ); + test( 'do not include first time contributors section if there are not any', () => { + expect( + getContributorProps( pullRequests.slice( 0, 4 ) ) + ).toMatchInlineSnapshot( `""` ); + } ); } ); describe( 'getContributorList', () => { diff --git a/bin/plugin/lib/utils.js b/bin/plugin/lib/utils.js index 4a75437a60694e..c50094321710ca 100644 --- a/bin/plugin/lib/utils.js +++ b/bin/plugin/lib/utils.js @@ -33,6 +33,7 @@ function runShellScript( script, cwd, env = {} ) { NO_CHECKS: 'true', PATH: process.env.PATH, HOME: process.env.HOME, + USER: process.env.USER, ...env, }, }, diff --git a/bin/plugin/utils/.wp-env.performance.json b/bin/plugin/utils/.wp-env.performance.json deleted file mode 100644 index 112c2684f64a48..00000000000000 --- a/bin/plugin/utils/.wp-env.performance.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "core": "WordPress/WordPress", - "plugins": [ "./plugin" ], - "themes": [ "../../tests/test/emptytheme" ], - "env": { - "tests": { - "mappings": { - "wp-content/mu-plugins": "../../tests/packages/e2e-tests/mu-plugins", - "wp-content/plugins/gutenberg-test-plugins": "../../tests/packages/e2e-tests/plugins" - } - } - } -} diff --git a/changelog.txt b/changelog.txt index 21306e569b4bda..dee5a1671cf931 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,1806 @@ == Changelog == += 15.8.0 = + +## Contributors + +The following contributors merged PRs in this release: + + +## Changelog + +### Features + +- Add the command center to the post editor. ([50128](https://github.com/WordPress/gutenberg/pull/50128)) +- Add revisions UI. ([50089](https://github.com/WordPress/gutenberg/pull/50089)) +- Add Theme Previews for block themes. ([50030](https://github.com/WordPress/gutenberg/pull/50030)) + + +### Enhancements + +#### Global Styles +- Fluid typography: Use `layout.wideSize` as max viewport width. ([49815](https://github.com/WordPress/gutenberg/pull/49815)) +- Global styles revisions: Remove human time diff and custom author fields from the API response. ([50234](https://github.com/WordPress/gutenberg/pull/50234)) +- Unify the global block styles panel with the block inspector panels. ([49428](https://github.com/WordPress/gutenberg/pull/49428)) + +#### Post Editor +- Add `` component. ([50260](https://github.com/WordPress/gutenberg/pull/50260)) +- Update background color of post editor when viewing mobile or tablet viewports, or in template mode. ([50361](https://github.com/WordPress/gutenberg/pull/50361)) +- Display device icon on "preview" and "view" buttons. ([50218](https://github.com/WordPress/gutenberg/pull/50218)) +- Improve "switch to draft" placement. ([50217](https://github.com/WordPress/gutenberg/pull/50217)) +- Post Featured Image: New design for Replace and Remove buttons. ([50269](https://github.com/WordPress/gutenberg/pull/50269)) + +#### Block Library +- More intuitive Details block with summary and innerBlocks content. ([49808](https://github.com/WordPress/gutenberg/pull/49808)) +- Simplify cover block description. ([50355](https://github.com/WordPress/gutenberg/pull/50355)) +- Cover: Only show overlay controls when color support enabled. ([50115](https://github.com/WordPress/gutenberg/pull/50115)) +- Navigation: Hide color controls when color support is disabled. ([50368](https://github.com/WordPress/gutenberg/pull/50368)) +- Post Featured Image: Hide overlay controls when color support is disabled. ([50331](https://github.com/WordPress/gutenberg/pull/50331)) +- Social Icons: Hide color controls when color support is disabled. ([50275](https://github.com/WordPress/gutenberg/pull/50275)) + +#### Fonts API +- Separate BC Layer. ([50077](https://github.com/WordPress/gutenberg/pull/50077)) + +#### Site Editor +- Add the pages menu to the site editor. ([50463](https://github.com/WordPress/gutenberg/pull/50463)) +- Introduce new PluginTemplateSettingPanel slot. ([50257](https://github.com/WordPress/gutenberg/pull/50257)) +- Add chevrons to the templates and template parts in site editor. ([50076](https://github.com/WordPress/gutenberg/pull/50076)) + +#### Components +- Button: Add opt-in prop for next 40px default size. ([50254](https://github.com/WordPress/gutenberg/pull/50254)) +- Consolidate and add documentation to Storybook. ([50226](https://github.com/WordPress/gutenberg/pull/50226)) +- DimensionControl: Use WordPress package instead of react in code example. ([50435](https://github.com/WordPress/gutenberg/pull/50435)) +- FormTokenField, ComboboxControl: Add opt-in prop for next 40px default size. ([50261](https://github.com/WordPress/gutenberg/pull/50261)) +- Global Styles: Fix palette color popovers. ([49975](https://github.com/WordPress/gutenberg/pull/49975)) +- Relax link pattern matching in CHANGELOG CI check. ([50248](https://github.com/WordPress/gutenberg/pull/50248)) +- Remove custom padding for tertiary buttons. ([50276](https://github.com/WordPress/gutenberg/pull/50276)) +- Update `Autocomplete` usage example. ([49965](https://github.com/WordPress/gutenberg/pull/49965)) +- Update default accent color. ([50193](https://github.com/WordPress/gutenberg/pull/50193)) +- Update has-text has-icon button spacing. ([50277](https://github.com/WordPress/gutenberg/pull/50277)) + +#### CSS & Styling +- Polish confirm dialog padding. ([50283](https://github.com/WordPress/gutenberg/pull/50283)) + +#### Template Editor +- Remove `start blank` option in template pattern suggestions and add `skip` button. ([50099](https://github.com/WordPress/gutenberg/pull/50099)) +- Templates: Sort by the rendered title instead of the slug. ([50353](https://github.com/WordPress/gutenberg/pull/50353)) + + +#### Block Editor +- List View: Allow dragging to all levels of the block hierarchy. ([49742](https://github.com/WordPress/gutenberg/pull/49742)) +- Allow dragging-and-dropping images from the inserter to image blocks. ([49673](https://github.com/WordPress/gutenberg/pull/49673)) +- Try always showing the dimensions controls. ([50305](https://github.com/WordPress/gutenberg/pull/50305)) +- Adds a renderAdditionalBlockUI prop to ListView. ([50465](https://github.com/WordPress/gutenberg/pull/50465)) +- Update the expanded text color so that it works in both a dark and light context. ([50434](https://github.com/WordPress/gutenberg/pull/50434)) +- Enable Loginout block in Nav block. ([49160](https://github.com/WordPress/gutenberg/pull/49160)) +- Mark related selector as resolved when sideloading Navigation fallback. ([50324](https://github.com/WordPress/gutenberg/pull/50324)) +- Update Template Parts icon to use the symbol. ([50410](https://github.com/WordPress/gutenberg/pull/50410)) +- Use Core Data for Nav fallbacks and side load resulting entity into state. ([50032](https://github.com/WordPress/gutenberg/pull/50032)) +- Tweak copy for block settings menu "Insert..." to "Add...". ([50444](https://github.com/WordPress/gutenberg/pull/50444)) +- Block settings menu: Rearrange items. ([50453](https://github.com/WordPress/gutenberg/pull/50453)) + +#### Rest API +- Update image editor to use new REST API. ([28530](https://github.com/WordPress/gutenberg/pull/28530)) + + + +### Bug Fixes + +#### Block Library +- Center align button text in editor. ([50228](https://github.com/WordPress/gutenberg/pull/50228)) +- Comment Template Block: Set `commentId` context via filter. ([50279](https://github.com/WordPress/gutenberg/pull/50279)) +- Cover: Use overflow: clip, falling back to overflow: Hidden to allow sticky children (technically). ([50237](https://github.com/WordPress/gutenberg/pull/50237)) +- Ensure imported Classic Menu dirty state to include in Editor entity changes list. ([50318](https://github.com/WordPress/gutenberg/pull/50318)) +- File Block: Defer hiding PDF embeds for unsupported browsers until the page has loaded. ([50113](https://github.com/WordPress/gutenberg/pull/50113)) +- Fix template part area variation matching. ([49500](https://github.com/WordPress/gutenberg/pull/49500)) +- Fix the pocket casts embed variation. ([50132](https://github.com/WordPress/gutenberg/pull/50132)) +- Gallery: Fix inner block selection. ([50363](https://github.com/WordPress/gutenberg/pull/50363)) +- Post Terms: Use publicly_queryable to query taxonomies used to register variations. ([50244](https://github.com/WordPress/gutenberg/pull/50244)) +- Remove extraneous "Link" copy from PanelBody components. ([50186](https://github.com/WordPress/gutenberg/pull/50186)) +- Remove Quote transform from Group ([50424](https://github.com/WordPress/gutenberg/pull/50424)) +- [Post Featured Image]: Revert maxWidth addition. ([50427](https://github.com/WordPress/gutenberg/pull/50427)) +- [Post Title]: Insert default block on Enter at end. ([50312](https://github.com/WordPress/gutenberg/pull/50312)) +- Ensure that view scripts are correctly registered for core blocks. ([50364](https://github.com/WordPress/gutenberg/pull/50364)) + +#### Global Styles +- Close stylebook if the editor canvas container slot is not filled. ([50086](https://github.com/WordPress/gutenberg/pull/50086)) +- Fix positioning of gradient palette popovers on mobile. ([50233](https://github.com/WordPress/gutenberg/pull/50233)) +- Fix/wp get global styles for custom props returns internal variable. ([50366](https://github.com/WordPress/gutenberg/pull/50366)) +- Revisions controller: Fix author and date fields. ([50117](https://github.com/WordPress/gutenberg/pull/50117)) +- Fix hover/focus styles for `style variation` buttons. ([50056](https://github.com/WordPress/gutenberg/pull/50056)) +- Use bundled files for enqueuing global styles. ([50310](https://github.com/WordPress/gutenberg/pull/50310)) + + +#### Block Editor +- Fix issue with margin collapsing when selecting blocks. ([50215](https://github.com/WordPress/gutenberg/pull/50215)) +- List View / Block Draggable: Fix scroll to top issue when dragging the second last block in the list. ([50119](https://github.com/WordPress/gutenberg/pull/50119)) +- Multi-select: Capitalise B in blocks. ([50356](https://github.com/WordPress/gutenberg/pull/50356)) +- URLInput: Update the 'ENTER' key down event handler. ([50158](https://github.com/WordPress/gutenberg/pull/50158)) +- Update OffCanvas component's more menu to get clientId from block rather than passed in as prop. ([50473](https://github.com/WordPress/gutenberg/pull/50473)) +- Block Toolbar: Don't use effects for focus management. ([50497](https://github.com/WordPress/gutenberg/pull/50497)) +- Don't remount the block when the 'templateLock' is set to 'contentOnly'. ([50292](https://github.com/WordPress/gutenberg/pull/50292)) + +#### Themes +- Refactor theme previews. ([50338](https://github.com/WordPress/gutenberg/pull/50338)) +- Remove gutenberg plugin mention in schema. ([50207](https://github.com/WordPress/gutenberg/pull/50207)) +- Theme Preview: Restrict to admin users. ([50335](https://github.com/WordPress/gutenberg/pull/50335)) +- Theme Previews: Fix refactor. ([50354](https://github.com/WordPress/gutenberg/pull/50354)) + +#### Site Editor +- Display conditionally the `styles` in sidebar main navigation screen. ([50329](https://github.com/WordPress/gutenberg/pull/50329)) +- Remove canvas box shadow. ([50374](https://github.com/WordPress/gutenberg/pull/50374)) +- Save Button: Fix the label in the disabled state. ([50284](https://github.com/WordPress/gutenberg/pull/50284)) +- Remove white background on Site Editor 'Frame'. ([48970](https://github.com/WordPress/gutenberg/pull/48970)) +- Update some visual details in the add-template modal(s). ([50143](https://github.com/WordPress/gutenberg/pull/50143)) + +#### Components +- Spacing: Fix reset of spacing sizes control. ([50455](https://github.com/WordPress/gutenberg/pull/50455)) +- `NavigableContailer`: Do not trap focus in `TabbableContainer`. ([49846](https://github.com/WordPress/gutenberg/pull/49846)) +- Remove fill="none" from levelUp icon. ([50264](https://github.com/WordPress/gutenberg/pull/50264)) + + +#### Accessibility +- Add missing tooltip to Site Editor navigation Back icon button. ([50104](https://github.com/WordPress/gutenberg/pull/50104)) +- Fix Multiple Tooltips from Focus Toolbar Shortcut on Widget Editor. ([50344](https://github.com/WordPress/gutenberg/pull/50344)) +- Site Editor Keyboard Navigation Flow. ([50296](https://github.com/WordPress/gutenberg/pull/50296)) +- Small improvements for the sidebars and Close buttons labeling. ([49614](https://github.com/WordPress/gutenberg/pull/49614)) + +#### Design Tools +- Changed so that borders and radius are maintained when Duotone is changed. ([50088](https://github.com/WordPress/gutenberg/pull/50088)) +- LineHeightControl: Fix application of zero values in editor. ([48917](https://github.com/WordPress/gutenberg/pull/48917)) + +#### Fonts API +- [Font API] Do not print in admin using 'admin_print_styles' for themes with theme.json. ([50259](https://github.com/WordPress/gutenberg/pull/50259)) + +#### Widget Editor +- Fixes fixed block toolbar in widgets editor. ([50371](https://github.com/WordPress/gutenberg/pull/50371)) + + +### Performance +- Improve Site Editor loading experience([50222](https://github.com/WordPress/gutenberg/pull/50222)) + + +### Experiments + +#### Interactivity API +- Add Interactivity API runtime. ([49994](https://github.com/WordPress/gutenberg/pull/49994)) +- Navigation block with the Interactivity API. ([50041](https://github.com/WordPress/gutenberg/pull/50041)) +- Fix building plugin zip to include interactive blocks. ([50598](https://github.com/WordPress/gutenberg/pull/50598)) + +#### Command Center +- Update the experiment label. ([50467](https://github.com/WordPress/gutenberg/pull/50467)) +- Add an API to open/close the command center. ([50423](https://github.com/WordPress/gutenberg/pull/50423)) +- Fix a style glitch on Safari. ([50138](https://github.com/WordPress/gutenberg/pull/50138)) +- Add a button to open it from the site editor view mode. ([50425](https://github.com/WordPress/gutenberg/pull/50425)) +- Do not show dynamic add new post, add new page commands. ([50221](https://github.com/WordPress/gutenberg/pull/50221)) +- Extract the WordPress reusable commands to a dedicated package. ([49956](https://github.com/WordPress/gutenberg/pull/49956)) +- Fix command menu not re-opening after closing it. ([50213](https://github.com/WordPress/gutenberg/pull/50213)) + + +### Documentation + +- Add changelog for eslint-plugin validating dependencies in `useSelect` and `useSuspenseSelect` hooks. ([50247](https://github.com/WordPress/gutenberg/pull/50247)) +- Add client-side filter reference to Curating the Editor doc and fix links. ([50203](https://github.com/WordPress/gutenberg/pull/50203)) +- Add missing closing parenthesis in code example. ([50253](https://github.com/WordPress/gutenberg/pull/50253)) +- Adds example to change the permalink structure. ([50251](https://github.com/WordPress/gutenberg/pull/50251)) +- Adds links to REST-API reference. ([50070](https://github.com/WordPress/gutenberg/pull/50070)) +- Create-block script includes link to documentation in render.php file. ([50206](https://github.com/WordPress/gutenberg/pull/50206)) +- Block API > Registration: Switch markdown to a-tags. ([50110](https://github.com/WordPress/gutenberg/pull/50110)) +- Don't use markdown in the callout section. ([50437](https://github.com/WordPress/gutenberg/pull/50437)) +- Remove mention of perf testing from release documentation. ([50345](https://github.com/WordPress/gutenberg/pull/50345)) +- Update Callout documentation on links. ([50131](https://github.com/WordPress/gutenberg/pull/50131)) +- Fix incorrect label in contributor docs. ([50201](https://github.com/WordPress/gutenberg/pull/50201)) +- Fix minor formatting issues in Performance doc. ([50202](https://github.com/WordPress/gutenberg/pull/50202)) +- Fixes incorrect URLs in doc blocks. ([50123](https://github.com/WordPress/gutenberg/pull/50123)) +- Use alert callout in the block deprecation documentation. ([50286](https://github.com/WordPress/gutenberg/pull/50286)) +- Improve CardMedia documentation. ([50074](https://github.com/WordPress/gutenberg/pull/50074)) +- Update README of TreeGrid with data-expanded attribute usage. ([50026](https://github.com/WordPress/gutenberg/pull/50026)) + + +### Code Quality + +- Block Editor: Directly import useShouldContextualToolbarShow hook. ([50506](https://github.com/WordPress/gutenberg/pull/50506)) +- BlockControls, InspectorControls: Remove useSlot, unify behavior on bad group. ([50198](https://github.com/WordPress/gutenberg/pull/50198)) +- Enqueue registered block assets and resolve iframe styles end-to-end failure. ([50185](https://github.com/WordPress/gutenberg/pull/50185)) +- Make eslint-plugin validate dependencies in `useSelect` and `useSuspenseSelect` hooks. ([49900](https://github.com/WordPress/gutenberg/pull/49900)) +- Save Hub: Reuse the save button component to save code. ([50145](https://github.com/WordPress/gutenberg/pull/50145)) +- Use apiFetch instead of window.fetch. ([50043](https://github.com/WordPress/gutenberg/pull/50043)) +- Add types to dispatch and select. ([49930](https://github.com/WordPress/gutenberg/pull/49930)) +- Remove duplicate comment. ([50304](https://github.com/WordPress/gutenberg/pull/50304)) + +#### Lodash removal +- Remove from blocks store reducer. ([50471](https://github.com/WordPress/gutenberg/pull/50471)) +- Remove `_.mapValues()` from `getBlockContentSchemaFromTransforms`. ([50096](https://github.com/WordPress/gutenberg/pull/50096)) +- Refactor away from `_.mapValues()`. ([50285](https://github.com/WordPress/gutenberg/pull/50285)) +- Remove `_.mapValues()` from blocks reducer. ([50097](https://github.com/WordPress/gutenberg/pull/50097)) + +#### Components +- Fix incorrect focus style widths. ([50127](https://github.com/WordPress/gutenberg/pull/50127)) +- Refactor/toolbar item component to typescript. ([49190](https://github.com/WordPress/gutenberg/pull/49190)) +- SlotFill: Several code cleanups. ([50098](https://github.com/WordPress/gutenberg/pull/50098)) +- SlotFill: Some code cleanups of the bubblesVirtually version. ([50133](https://github.com/WordPress/gutenberg/pull/50133)) +- Convert `NavigableContainer` to TypeScript. ([49377](https://github.com/WordPress/gutenberg/pull/49377)) + +#### Block Editor +- Block Editor: Add names for the 'editor.BlockListBlock' filter HoCs. ([50513](https://github.com/WordPress/gutenberg/pull/50513)) +- Refactor around missing dependency in Link Control internal value sync effect. ([49509](https://github.com/WordPress/gutenberg/pull/49509)) +- Don't export Slot/Fill constants separately. ([50274](https://github.com/WordPress/gutenberg/pull/50274)) + +#### Block Library +- Centralize constants for the navigation block. ([50191](https://github.com/WordPress/gutenberg/pull/50191)) +- Move deprecated Nav block functions to bottom of render file. ([50328](https://github.com/WordPress/gutenberg/pull/50328)) +- Refactor BlockList uses Hooks. ([50293](https://github.com/WordPress/gutenberg/pull/50293)) + +#### List View +- Replace OffCanvasEditor in browse mode with the List View component. ([50287](https://github.com/WordPress/gutenberg/pull/50287)) +- Use block select button class for retaining colors when expanded or hovered. ([50155](https://github.com/WordPress/gutenberg/pull/50155)) + +#### Global Styles +- Global styles revisions: Ensure the revisions endpoint permissions match the global styles controller. ([50270](https://github.com/WordPress/gutenberg/pull/50270)) +- Simplify CustomCSS UI. ([49721](https://github.com/WordPress/gutenberg/pull/49721)) + +#### Plugin +- Update REST API Controller PHPUnit tests. ([50120](https://github.com/WordPress/gutenberg/pull/50120)) +- Update the Gutenberg plugin to require at least the WP 6.1 version. ([50079](https://github.com/WordPress/gutenberg/pull/50079)) +- Update the tested up to version for the gutenberg plugin. ([50360](https://github.com/WordPress/gutenberg/pull/50360)) + +#### Native +- Add a few import required for the React Native wrapper distribution as iOS XCFramework. ([50009](https://github.com/WordPress/gutenberg/pull/50009)) + +#### Site Editor +- Extract the Router APIs and context into a dedicated package. ([50100](https://github.com/WordPress/gutenberg/pull/50100)) +- Move loading logic to a separate hook. ([50511](https://github.com/WordPress/gutenberg/pull/50511)) + + + +### Tools + +#### wp-env +- Add `wp-env` After Setup Command. ([50196](https://github.com/WordPress/gutenberg/pull/50196)) +- Fix wp-env destroy. ([50445](https://github.com/WordPress/gutenberg/pull/50445)) +- Refactored `wp-env` configuration Parsing. ([50071](https://github.com/WordPress/gutenberg/pull/50071)) +- Wp-env fix exec command in CI. ([50411](https://github.com/WordPress/gutenberg/pull/50411)) +- wp-env: Add better default PHPunit settings, fix Xdebug, and update documentation. ([50490](https://github.com/WordPress/gutenberg/pull/50490)) +- wp-env: Improve run command execution speed. ([50007](https://github.com/WordPress/gutenberg/pull/50007)) +- Cleaned up Port Validation and Removed Unnecessary Default Ports. ([50300](https://github.com/WordPress/gutenberg/pull/50300)) +- Match Container's User and Group to Host. ([49962](https://github.com/WordPress/gutenberg/pull/49962)) + +#### Testing +- Add end-to-end test for editing the title of a new custom template part. ([50195](https://github.com/WordPress/gutenberg/pull/50195)) +- Add test to verify image appears on frontend. ([50472](https://github.com/WordPress/gutenberg/pull/50472)) +- Add tests coverage for the navigation block frontend interactivity. ([50462](https://github.com/WordPress/gutenberg/pull/50462)) +- Migrate Adding Patterns Test to Playwright. ([50083](https://github.com/WordPress/gutenberg/pull/50083)) +- Migrate Keep Transform Block Test Case to Playwright. ([49719](https://github.com/WordPress/gutenberg/pull/49719)) +- Migrate PullQuote test case to playwright. ([50085](https://github.com/WordPress/gutenberg/pull/50085)) +- Migrate undo to Playwright. ([48701](https://github.com/WordPress/gutenberg/pull/48701)) +- Prerelease end-to-end Test Utils for Playwright. ([43998](https://github.com/WordPress/gutenberg/pull/43998)) +- Skip creating flaky issues on PRs. ([50146](https://github.com/WordPress/gutenberg/pull/50146)) +- end-to-end tests: Try to fix flaky global styles revisions tests. ([50454](https://github.com/WordPress/gutenberg/pull/50454)) +- Add BlockList text coverage. ([50252](https://github.com/WordPress/gutenberg/pull/50252)) + +#### Build Tooling +- Changelog automation: Apply proper top-level categorization precedence to meta PRs. ([50208](https://github.com/WordPress/gutenberg/pull/50208)) +- Do not include first time contributors section in changelog if there are not any. ([50291](https://github.com/WordPress/gutenberg/pull/50291)) +- Publish rich text build types. ([49651](https://github.com/WordPress/gutenberg/pull/49651)) +- Rich text: Rename index.js > index.ts for type-only exports. ([50212](https://github.com/WordPress/gutenberg/pull/50212)) +- Remove PHPUnit and Composer packages. ([50408](https://github.com/WordPress/gutenberg/pull/50408)) + +#### Repository Maintainance +- Add ndiego as codeowner for docs. ([50289](https://github.com/WordPress/gutenberg/pull/50289)) + + +## First time contributors + +The following PRs were merged by first time contributors: + +- @johnhooks: feature(data): Add types to dispatch and select. ([49930](https://github.com/WordPress/gutenberg/pull/49930)) +- @margolisj: Refactor/toolbar item component to typescript. ([49190](https://github.com/WordPress/gutenberg/pull/49190)) +- @n2erjo00: Create-block script includes link to documentation in render.php file. ([50206](https://github.com/WordPress/gutenberg/pull/50206)) +- @samnajian: Fix/wp get global styles for custom props returns internal variable. ([50366](https://github.com/WordPress/gutenberg/pull/50366)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @afercia @ajlende @alexstine @andrewserong @apeatling @artemiomorales @aurooba @bph @chad1008 @ciampo @DAreRodz @dcalhoun @draganescu @ecgan @fluiddot @fullofcaffeine @geriux @getdave @glendaviesnz @gziolo @hellofromtonya @ironprogrammer @jameskoster @jasmussen @jeryj @jhnstn @johnhooks @jsnajdr @juanfra @kevin940726 @kienstra @Mamaduka @margolisj @mburridge @mirka @mokagio @mtias @n2erjo00 @ndiego @noahtallen @noisysocks @ntsekouras @oandregal @ObliviousHarmony @ocean90 @ockham @pavanpatil1 @pooja-muchandikar @priethor @ramonjd @richtabor @samnajian @SantosGuillamot @scruffian @SiobhyB @t-hamano @talldan @tellthemachines @torounit @tyxla @westonruter @youknowriad + + + + += 15.7.1 = + + + +## Changelog + +### Enhancements + +#### Fonts API +- Relocate which fonts to print into wp_print_fonts(). ([50151](https://github.com/WordPress/gutenberg/pull/50151)) + + +### Tools + +#### Testing +- Skip iframe-inline-styles end-to-end test. ([50320](https://github.com/WordPress/gutenberg/pull/50320)) + +## Contributors + +The following contributors merged PRs in this release: + +@hellofromtonya @scruffian + + += 15.7.0 = + +## Changelog + +### Enhancements + +#### Block Library +- Group: Add allowedBlocks attribute and pass it to innerBlockProps. ([49128](https://github.com/WordPress/gutenberg/pull/49128)) +- Media & Text: Add allowedBlocks attribute and pass it to innerBlock. ([49981](https://github.com/WordPress/gutenberg/pull/49981)) +- Site Logo: Add logo replace flow in Inspector controls. ([49992](https://github.com/WordPress/gutenberg/pull/49992)) + +#### Global Styles +- Fluid typography: Use logarithmic scale factor to calculate a min font size. ([49707](https://github.com/WordPress/gutenberg/pull/49707)) +- Style book: Make the style book slot generic. ([49973](https://github.com/WordPress/gutenberg/pull/49973)) +- Base Styles: Add the editor input reset with increased specificity. ([49831](https://github.com/WordPress/gutenberg/pull/49831)) + +#### Components +- Draggable: Allow elementId based elements to be attached to the ownerDocument body. ([49911](https://github.com/WordPress/gutenberg/pull/49911)) +- Spinner: Enforce no background. ([49695](https://github.com/WordPress/gutenberg/pull/49695)) +- Updates the behavior of the top toolbar fixed setting. ([49634](https://github.com/WordPress/gutenberg/pull/49634)) + +#### Design Tools +- Image: Display custom borders on placeholder. ([49569](https://github.com/WordPress/gutenberg/pull/49569)) +- Patterns: Try a masonry layout on the template pattern suggestion modal. ([49958](https://github.com/WordPress/gutenberg/pull/49958)) + +#### Duotone +- Duotone: Add block controls on the inspector. ([49838](https://github.com/WordPress/gutenberg/pull/49838)) +- Duotone: Fix setup state for image block. ([49949](https://github.com/WordPress/gutenberg/pull/49949)) +- Polish duotone rendering code. ([49706](https://github.com/WordPress/gutenberg/pull/49706)) +- Deprecate remaining global duotone functions. ([49702](https://github.com/WordPress/gutenberg/pull/49702)) +- Group duotone outputs and refactor rendering. ([49705](https://github.com/WordPress/gutenberg/pull/49705)) + +### Accessibility +- List view: Refactor ARIA attributes. ([48461](https://github.com/WordPress/gutenberg/pull/48461)) +- Block Mover: Make text labels for left/right move buttons legible. ([49747](https://github.com/WordPress/gutenberg/pull/49747)) +- Block Toolbar: Fix incorrect switcher button width in text mode. ([49847](https://github.com/WordPress/gutenberg/pull/49847)) +- Snackbar: Fix insufficient color contrast on hover. ([49682](https://github.com/WordPress/gutenberg/pull/49682)) +- Update Reakit dep to 1.3.11. ([49763](https://github.com/WordPress/gutenberg/pull/49763)) +- List View: Add aria-description. ([49907](https://github.com/WordPress/gutenberg/pull/49907)) + +### Bug Fixes + +#### Block Library + +- Cover block: Remove overflow hidden rule. ([49913](https://github.com/WordPress/gutenberg/pull/49913)) +- Fix BlockInfo slot displaying logic. ([50054](https://github.com/WordPress/gutenberg/pull/50054)) +- Fix block toolbar height and rounded corners of parent selector button when text label mode. ([49556](https://github.com/WordPress/gutenberg/pull/49556)) +- Demo content cover block alignment not respected. ([49848](https://github.com/WordPress/gutenberg/pull/49848)) +- Group block: Remove innerprops from placeholder wrapper. ([49783](https://github.com/WordPress/gutenberg/pull/49783)) +- Only display the modified post date if the post has been modified. ([46839](https://github.com/WordPress/gutenberg/pull/46839)) +- Post Excerpt: Fix crash at runtime when postType is undefined. ([49899](https://github.com/WordPress/gutenberg/pull/49899)) +- Post Featured Image: Fix some sizing issues. ([49641](https://github.com/WordPress/gutenberg/pull/49641)) +- Adjust copy of Site Logo Block. ([49540](https://github.com/WordPress/gutenberg/pull/49540)) +- Adapt flex child controls for Spacer. ([49362](https://github.com/WordPress/gutenberg/pull/49362)) +- Social Icon: Update the `link` and `mail` block variation's icons. ([49952](https://github.com/WordPress/gutenberg/pull/49952)) +- Cover: Re-instate overflow:Hidden rule to fix issue with border radius. ([50209](https://github.com/WordPress/gutenberg/pull/50209)) +- Fix site logo preview image size with long filenames. ([50242](https://github.com/WordPress/gutenberg/pull/50242)) + +#### Components +- CheckboxControl: Add support custom IDs. ([49977](https://github.com/WordPress/gutenberg/pull/49977)) +- Equalize modal internal padding. ([49890](https://github.com/WordPress/gutenberg/pull/49890)) +- Increase modal radius. ([49870](https://github.com/WordPress/gutenberg/pull/49870)) +- Query: Fix add new post link position via private SlotFill. ([49819](https://github.com/WordPress/gutenberg/pull/49819)) +- Tweak `WordPressComponent` type. ([49960](https://github.com/WordPress/gutenberg/pull/49960)) +- Update the framer motion dependency to the latest version. ([49822](https://github.com/WordPress/gutenberg/pull/49822)) +- Improve output of CHANGELOG CI check. ([49779](https://github.com/WordPress/gutenberg/pull/49779)) +- Retain context when opening modals on small screens. ([50039](https://github.com/WordPress/gutenberg/pull/50039)) +- Update CHANGELOG CI check to support forked repos. ([49906](https://github.com/WordPress/gutenberg/pull/49906)) + +#### Global Styles +- Borders: Fix border style on color/width clearing and global styles fallback logic. ([49738](https://github.com/WordPress/gutenberg/pull/49738)) +- Borders: Maintain radius in Global Styles. ([49950](https://github.com/WordPress/gutenberg/pull/49950)) +- Close stylebook if the global styles side bar is not open. ([50081](https://github.com/WordPress/gutenberg/pull/50081)) +- Do not add unregistered style variations to the theme.json schema. ([49807](https://github.com/WordPress/gutenberg/pull/49807)) +- Update preset styles to use Selectors API. ([49427](https://github.com/WordPress/gutenberg/pull/49427)) +- Change the 'WP_Theme_JSON_Data_Gutenberg' class directory. ([50062](https://github.com/WordPress/gutenberg/pull/50062)) +- Layout: Fix issue where saving user global styles included layout definitions in layout settings. ([50268](https://github.com/WordPress/gutenberg/pull/50268)) + +#### Site Editor +- Fix screen flash when deleting templates in templates list. ([48449](https://github.com/WordPress/gutenberg/pull/48449)) +- Fix the condition for the modal to choose the initial template pattern. ([49954](https://github.com/WordPress/gutenberg/pull/49954)) +- Fix editor crash caused by missing type conversion in EditorStyles component. ([49882](https://github.com/WordPress/gutenberg/pull/49882)) +- Remove frame shadow in edit view. ([49767](https://github.com/WordPress/gutenberg/pull/49767)) +- Add `home` to list of new templates. ([47389](https://github.com/WordPress/gutenberg/pull/47389)) +- Animate the site icon element between view and edit in the site editor. ([48886](https://github.com/WordPress/gutenberg/pull/48886)) +- Restore click event handler on site icon button. ([50094](https://github.com/WordPress/gutenberg/pull/50094)) +- Correctly return 'isResolving' from 'useAlternativeTemplateParts' hook. ([49921](https://github.com/WordPress/gutenberg/pull/49921)) + +#### Patterns +- Increase the dimensions of the pattern modal that appears when creating a new page. ([49859](https://github.com/WordPress/gutenberg/pull/49859)) +- Update full screen modal dimensions, and pattern grids. ([49894](https://github.com/WordPress/gutenberg/pull/49894)) +- Increase pattern modal dimensions when creating a new template. ([49722](https://github.com/WordPress/gutenberg/pull/49722)) + +#### List View +- Add parameters to custom-scrollbars-on-hover. ([49892](https://github.com/WordPress/gutenberg/pull/49892)) +- Update with scrolling and custom scrollbar. ([49793](https://github.com/WordPress/gutenberg/pull/49793)) +- Ensure list view block id is unique to the list view instance. ([49944](https://github.com/WordPress/gutenberg/pull/49944)) + +#### Block Editor +- Fix quick inserter going off-screen in some situations. ([49881](https://github.com/WordPress/gutenberg/pull/49881)) +- List View: Update drop indicator width to be aware of scroll containers. ([49786](https://github.com/WordPress/gutenberg/pull/49786)) +- Block highlight: Fix radius issue. ([49864](https://github.com/WordPress/gutenberg/pull/49864)) +- DOM: Update getScrollContainer to be aware of horizontal scroll. ([49787](https://github.com/WordPress/gutenberg/pull/49787)) +- Fix fixed block toolbar positioning. ([49990](https://github.com/WordPress/gutenberg/pull/49990)) +- iframe: Add `enqueue_block_assets`. ([49655](https://github.com/WordPress/gutenberg/pull/49655)) +- Rename immutableSet to setImmutably. ([50040](https://github.com/WordPress/gutenberg/pull/50040)) +- Edit Post: Hide overflowing content in visual editor wrapper to prevent block popovers from creating scrollbars. ([49978](https://github.com/WordPress/gutenberg/pull/49978)) + +#### REST API +- Replace fallbacks to fallback (singular) in Nav fallback REST endpoint. ([50044](https://github.com/WordPress/gutenberg/pull/50044)) +- Consolidate Navigation fallbacks logic between Editor and Front of Site via REST API. ([48698](https://github.com/WordPress/gutenberg/pull/48698)) +- Add /revisions endpoint for global styles. ([49974](https://github.com/WordPress/gutenberg/pull/49974)) + +#### Build Tooling +- Fix multiple tooltips showing on NavigableToolbars. ([49644](https://github.com/WordPress/gutenberg/pull/49644)) +- Add `--env-cwd` Option To `wp-env run`. ([49908](https://github.com/WordPress/gutenberg/pull/49908)) - Add Port To `WP_TESTS_DOMAIN`. ([49883](https://github.com/WordPress/gutenberg/pull/49883)) +- Add support in `check-license` for conjunctive (AND) licenses. ([46801](https://github.com/WordPress/gutenberg/pull/46801)) + +### Performance +Continued the work refactor away from Lodash usages to reduce the build size +[49799](https://github.com/WordPress/gutenberg/pull/49799), [49794](https://github.com/WordPress/gutenberg/pull/49794), [49755](https://github.com/WordPress/gutenberg/pull/49755) + +### Experiments +- Allow adding posts and pages with custom titles from the command menu. ([49893](https://github.com/WordPress/gutenberg/pull/49893)) +- Update the design of the command center. ([49681](https://github.com/WordPress/gutenberg/pull/49681)) + +### Documentation +- Adds closing code tags. ([49991](https://github.com/WordPress/gutenberg/pull/49991)) +- Add "block-selectors" documentation to TOC and manifest. ([49471](https://github.com/WordPress/gutenberg/pull/49471)) +- Adds note about custom fields to Plugin Sidebar page in documentation. ([49622](https://github.com/WordPress/gutenberg/pull/49622)) +- Autocomplete: Add heading and fix type for `onReplace` in README. ([49798](https://github.com/WordPress/gutenberg/pull/49798)) +- Document the separation between private APIs and experimental APIs. ([47973](https://github.com/WordPress/gutenberg/pull/47973)) +- Fix broken link and update title of Localizing Gutenberg doc. ([49851](https://github.com/WordPress/gutenberg/pull/49851)) +- Fixes broken img link. ([49805](https://github.com/WordPress/gutenberg/pull/49805)) +- Need to use WordPress package for useState. ([49478](https://github.com/WordPress/gutenberg/pull/49478)) +- Remove unused screenshot from documentation folder. ([49896](https://github.com/WordPress/gutenberg/pull/49896)) +- Small grammar fixes for block-context.md. ([49701](https://github.com/WordPress/gutenberg/pull/49701)) +- Update IS_GUTENBERG_PLUGIN to process.env.IS_GUTENBERG_PLUGIN in coding guidelines. ([49825](https://github.com/WordPress/gutenberg/pull/49825)) +- Fixes typo in README. ([49957](https://github.com/WordPress/gutenberg/pull/49957)) +- Update README.md. ([49762](https://github.com/WordPress/gutenberg/pull/49762)) +- Update README.md. ([49855](https://github.com/WordPress/gutenberg/pull/49855)) +- Update outreach.md. ([49961](https://github.com/WordPress/gutenberg/pull/49961)) +- Update wp-env prereq documentation. ([50004](https://github.com/WordPress/gutenberg/pull/50004)) +- Formatting inside alert div. ([50090](https://github.com/WordPress/gutenberg/pull/50090)) +#### Theme.json +- Add documentation for `variations` key in `theme.json`. ([49826](https://github.com/WordPress/gutenberg/pull/49826)) +- Update theme.json reference documentation to include more sections. ([48250](https://github.com/WordPress/gutenberg/pull/48250)) + +#### Gutenberg Plugin release +- Update the Gutenberg release issue template to reflect the new Gutenberg release team. ([50037](https://github.com/WordPress/gutenberg/pull/50037)) +- Update Gutenberg release documentation to include the new Gutenberg Release Team. ([50036](https://github.com/WordPress/gutenberg/pull/50036)) +- Fix broken link and minor tweaks to formatting and verbiage in the Gutenberg Release Process doc. ([49876](https://github.com/WordPress/gutenberg/pull/49876)) +- Update release checklist. ([50068](https://github.com/WordPress/gutenberg/pull/50068)) + +#### Block Development +- Improve the learning experience for writing blocks. ([49792](https://github.com/WordPress/gutenberg/pull/49792)) +- Re-write of the landing page. ([49643](https://github.com/WordPress/gutenberg/pull/49643)) +- Improve insertBlock(s) documentation. ([50078](https://github.com/WordPress/gutenberg/pull/50078)) +- Small Typo: Remove dots. ([49853](https://github.com/WordPress/gutenberg/pull/49853)) + +### Code Quality + +- Fix PrivateInserter import. ([50038](https://github.com/WordPress/gutenberg/pull/50038)) +- Remove the normalizeFalsyValue util. ([50033](https://github.com/WordPress/gutenberg/pull/50033)) +- Don't programmatically lowercase post type label. ([49591](https://github.com/WordPress/gutenberg/pull/49591)) +- Port colord to PHP. ([49700](https://github.com/WordPress/gutenberg/pull/49700)) +- Reuse cleanEmptyObject utility and fix empty string case. ([49750](https://github.com/WordPress/gutenberg/pull/49750)) +- toStyles: Don't mutate the tree argument. ([49806](https://github.com/WordPress/gutenberg/pull/49806)) +- useInstanceId: Fix useMemo hook dependencies. ([49979](https://github.com/WordPress/gutenberg/pull/49979)) + +### Tools + +#### Testing +- Components: Update `ColorPicker` unit tests. ([49698](https://github.com/WordPress/gutenberg/pull/49698)) +- Migrate CPT end-to-end tests to Playwright. ([50031](https://github.com/WordPress/gutenberg/pull/50031)) +- Fonts API: Add tests for gutenberg_add_registered_fonts_to_theme_json(). ([50049](https://github.com/WordPress/gutenberg/pull/50049)) +- Expand multi-line block tests. ([49732](https://github.com/WordPress/gutenberg/pull/49732)) +- Rich text test helpers mimic user events. ([49804](https://github.com/WordPress/gutenberg/pull/49804)) +- Fix editor canvas detaching error in end-to-end tests. ([49374](https://github.com/WordPress/gutenberg/pull/49374)) + +#### Build Tooling +- Add script which checks published types for non-checked JS packages. ([49736](https://github.com/WordPress/gutenberg/pull/49736)) +- Docgen: Fix issue where function token can't be found. ([49812](https://github.com/WordPress/gutenberg/pull/49812)) +- Publish build types for notices. ([49650](https://github.com/WordPress/gutenberg/pull/49650)) +- Publish types for plugins package. ([49649](https://github.com/WordPress/gutenberg/pull/49649)) + +## First time contributors + +The following PRs were merged by first time contributors: + +- @annziel: Fix screen flash when deleting templates in templates list. ([48449](https://github.com/WordPress/gutenberg/pull/48449)) +- @courtneyr-dev: Update outreach.md. ([49961](https://github.com/WordPress/gutenberg/pull/49961)) +- @m0hanraj: Fixes typo in README. ([49957](https://github.com/WordPress/gutenberg/pull/49957)) +- @MahendraBishnoi29: Snackbar: Fix insufficient color contrast on hover. ([49682](https://github.com/WordPress/gutenberg/pull/49682)) +- @masteradhoc: Adjust copy of Site Logo Block. ([49540](https://github.com/WordPress/gutenberg/pull/49540)) +- @sque89: Group: Add allowedBlocks attribute and pass it to innerBlockProps. ([49128](https://github.com/WordPress/gutenberg/pull/49128)) +- @tyrann0us: Don't programmatically lowercase post type label. ([49591](https://github.com/WordPress/gutenberg/pull/49591)) +- @Wtower: Update README.md. ([49855](https://github.com/WordPress/gutenberg/pull/49855)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @adamziel @ajlende @alexstine @andrewserong @annziel @aurooba @bdurette @bph @carolinan @chad1008 @chintu51 @courtneyr-dev @dcalhoun @derekblank @draganescu @ellatrix @fluiddot @gaambo @geriux @getdave @gigitux @guarani @gvgvgvijayan @gziolo @hellofromtonya @jameskoster @jasmussen @jeryj @jhnstn @jsnajdr @juanmaguitar @kevin940726 @m0hanraj @MaggieCabrera @MahendraBishnoi29 @Mamaduka @masteradhoc @mburridge @mcsf @mikachan @mirka @mokagio @mtias @ndiego @noahtallen @ntsekouras @oandregal @ObliviousHarmony @priethor @ramonjd @scruffian @SiobhyB @Soean @sque89 @t-hamano @talldan @tellthemachines @tyrann0us @tyxla @Wtower @youknowriad @zzap + + + + += 15.6.2 = + + + +## Changelog + +### Bug Fixes + +- Post Template: Fix broken title editing. ([49984](https://github.com/WordPress/gutenberg/pull/49984)) + + +## First time contributors + +The following PRs were merged by first time contributors: + + + +## Contributors + +The following contributors merged PRs in this release: + +@jsnajdr + + += 15.6.1 = + +## Changelog + +### Bug Fixes + +#### CSS & Styling +- Cover: Reduce z-index for inner blocks container. ([49943](https://github.com/WordPress/gutenberg/pull/49943)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw + + += 15.6.0 = + +## Changelog + +### Enhancements + +#### Block Library +- A margin option is added to the Embedded preview block. ([39384](https://github.com/WordPress/gutenberg/pull/39384)) +- Add blockGap as a default control on Columns/Column blocks. ([49526](https://github.com/WordPress/gutenberg/pull/49526)) +- Add color tools to the Time to Read block. ([49496](https://github.com/WordPress/gutenberg/pull/49496)) +- Add spacing presets to the Spacer block. ([44002](https://github.com/WordPress/gutenberg/pull/44002)) +- Cover: Add border support using ResizableBox via BlockPopover. ([41153](https://github.com/WordPress/gutenberg/pull/41153)) + +#### Block Editor +- Add support for hiding breadcrumbs in the Site Editor. ([49518](https://github.com/WordPress/gutenberg/pull/49518)) +- Hide page list on single page list menus. ([48725](https://github.com/WordPress/gutenberg/pull/48725)) +- Small improvements for the Document Overview labeling. ([49555](https://github.com/WordPress/gutenberg/pull/49555)) +- Add a rootClientId prop. ([49475](https://github.com/WordPress/gutenberg/pull/49475)) + +#### Global Styles +- Add styles section to the browse mode sidebar. ([49014](https://github.com/WordPress/gutenberg/pull/49014)) +- Show child layout controls by default. ([49389](https://github.com/WordPress/gutenberg/pull/49389)) + +#### Components +- Global Styles: Link to the block type panel when selecting a block with Styles open. ([49350](https://github.com/WordPress/gutenberg/pull/49350)) +- TreeSelect: Remove margin overrides and add new opt-in prop. ([47515](https://github.com/WordPress/gutenberg/pull/47515)) +- Update media and video icons. ([49523](https://github.com/WordPress/gutenberg/pull/49523)) + +### Accessibility + +- Add missing focus style to start template options previews. ([49334](https://github.com/WordPress/gutenberg/pull/49334)) +- Consistent labels for the WP logo and back links. ([49659](https://github.com/WordPress/gutenberg/pull/49659)) + +### Experiments + +- Add a command center to the site editor. ([49330](https://github.com/WordPress/gutenberg/pull/49330)) +- Add Details/summary block. ([45055](https://github.com/WordPress/gutenberg/pull/45055)) + +### Bug Fixes + +#### Block Library +- Columns: Prevent removal of locked columns from the column count change UI. ([49530](https://github.com/WordPress/gutenberg/pull/49530)) +- Cover: Fixed background causes image to be zoomed in on Safari browser on iPad. ([48981](https://github.com/WordPress/gutenberg/pull/48981)) +- Cover: Fix integration tests for ResizableBox via BlockPopover. ([49497](https://github.com/WordPress/gutenberg/pull/49497)) +- Cover: Merge block and global styles. ([49434](https://github.com/WordPress/gutenberg/pull/49434)) +- Fix exhaustive deps warning. ([49291](https://github.com/WordPress/gutenberg/pull/49291)) +- Fix fallback not rendering. ([49576](https://github.com/WordPress/gutenberg/pull/49576)) +- Image block: Fix image size control percentage selection. ([49628](https://github.com/WordPress/gutenberg/pull/49628)) +- Post Featured Image: Don't display the scale control when the aspect ratio is original. ([48894](https://github.com/WordPress/gutenberg/pull/48894)) +- Post Excerpt: Ensure the postId from the block context is used to `get_the_excerpt`. ([49495](https://github.com/WordPress/gutenberg/pull/49495)) +- Shortcode: Prevent external styling of editing UI. ([49723](https://github.com/WordPress/gutenberg/pull/49723)) +- Time To Read: Fix untranslated on the front end. ([49704](https://github.com/WordPress/gutenberg/pull/49704)) +- Post Featured Image: Show all controls when in context without `postId`. ([49609](https://github.com/WordPress/gutenberg/pull/49609)) +- Avoid empty Gallery block error. ([49557](https://github.com/WordPress/gutenberg/pull/49557)) + +#### Site Editor +- Decode the site title properly. ([49685](https://github.com/WordPress/gutenberg/pull/49685)) +- Show UI to update custom links on the sidebar navigation. ([48939](https://github.com/WordPress/gutenberg/pull/48939)) +- Navigate to `editor` when clicking `Edit` in a template part. ([49604](https://github.com/WordPress/gutenberg/pull/49604)) +- Update the template title in the details panel. ([49487](https://github.com/WordPress/gutenberg/pull/49487)) +- Use the keyboard shortcuts package for the command center. ([49680](https://github.com/WordPress/gutenberg/pull/49680)) +- Fix the site editor loading in multi-site installs. ([49861](https://github.com/WordPress/gutenberg/pull/49861)) + +#### Global Styles +- Block Level Settings: Remove WP filters in Gutenberg. ([49598](https://github.com/WordPress/gutenberg/pull/49598)) +- Border Panel: Move global styles forcing of split borders to relevant panel's onChange. ([49603](https://github.com/WordPress/gutenberg/pull/49603)) +- Remove the pointer cursor on the style variation preview. ([49573](https://github.com/WordPress/gutenberg/pull/49573)) +- Style Variation Picker: Restore pointer cursor when withHover is in use. ([49596](https://github.com/WordPress/gutenberg/pull/49596)) + +#### Block Editor +- DropZone: Smooth animation. ([49517](https://github.com/WordPress/gutenberg/pull/49517)) +- List View: Allow dragging underneath collapsed non-empty container blocks. ([49390](https://github.com/WordPress/gutenberg/pull/49390)) +- List View: Update drop indicator line to be 4px high with a white border. ([49462](https://github.com/WordPress/gutenberg/pull/49462)) +- Tweak remove block label to make it simpler. ([49529](https://github.com/WordPress/gutenberg/pull/49529)) +- Fix redirected links to user documents. ([49537](https://github.com/WordPress/gutenberg/pull/49537)) +- Make InspectorControls tab labels translatable. ([49665](https://github.com/WordPress/gutenberg/pull/49665)) +- Update redirected support link. ([49533](https://github.com/WordPress/gutenberg/pull/49533)) +- Reusable Block: RTL styles don't get added to the `wp-reusable-blocks` stylesheet. ([49632](https://github.com/WordPress/gutenberg/pull/49632)) +- Fix padding and margin visualizer accuracy. ([49422](https://github.com/WordPress/gutenberg/pull/49422)) + +#### Layout +- Only show alignment info when parent layout is constrained. ([49703](https://github.com/WordPress/gutenberg/pull/49703)) +- Try reducing the specificity of layout margin rules. ([47858](https://github.com/WordPress/gutenberg/pull/47858)) + +#### Post Editor +- Allow root padding setting to affect padding of post title. ([49460](https://github.com/WordPress/gutenberg/pull/49460)) +- Try fixing post editor layout for wide/full Post Content blocks. ([49565](https://github.com/WordPress/gutenberg/pull/49565)) +- Allow horizontal scroll in panels in the post editor. ([49611](https://github.com/WordPress/gutenberg/pull/49611)) + +#### Components +- Fix inbetween-inserter position in RTL languages. ([49683](https://github.com/WordPress/gutenberg/pull/49683)) +- Update Edit icon to be more consistent with other icons. ([49512](https://github.com/WordPress/gutenberg/pull/49512)) +- Update search icon to be optically more balanced. ([49425](https://github.com/WordPress/gutenberg/pull/49425)) +- Update some icons to address aliasing on high-res screens. ([49381](https://github.com/WordPress/gutenberg/pull/49381)) + +### Performance + +- Continued the work refactor away from Lodash usages to reduce the build size +([49725](https://github.com/WordPress/gutenberg/pull/49725), [49724](https://github.com/WordPress/gutenberg/pull/49724), [49638](https://github.com/WordPress/gutenberg/pull/49638), [49654](https://github.com/WordPress/gutenberg/pull/49654), [49639](https://github.com/WordPress/gutenberg/pull/49639), [49637](https://github.com/WordPress/gutenberg/pull/49637), [49727](https://github.com/WordPress/gutenberg/pull/49727)) + +### Documentation + +- Add section about integration testing to testing documentation. ([49454](https://github.com/WordPress/gutenberg/pull/49454)) +- Add some documentation about the Styles UI components. ([49720](https://github.com/WordPress/gutenberg/pull/49720)) +- Fix Create a Block Tutorial link on blocks package README.md. ([49562](https://github.com/WordPress/gutenberg/pull/49562)) +- Fix end-to-end command typos. ([49669](https://github.com/WordPress/gutenberg/pull/49669)) +- Fix line break issue in Schemas and Content Models section. ([44768](https://github.com/WordPress/gutenberg/pull/44768)) +- Fixes some typos. ([49472](https://github.com/WordPress/gutenberg/pull/49472)) +- Update outdated references in react-native-editor documentation. ([49543](https://github.com/WordPress/gutenberg/pull/49543)) +- `getBlockOrder()` - use core/block-editor instead of core/editor. ([49474](https://github.com/WordPress/gutenberg/pull/49474)) +- Fix Xcode spelling across the codebase. ([49625](https://github.com/WordPress/gutenberg/pull/49625)) +- Remove the documentation for Playwright configuration override. ([49660](https://github.com/WordPress/gutenberg/pull/49660)) +- Streamlining the New Release Issue Template. ([49618](https://github.com/WordPress/gutenberg/pull/49618)) +- Update components package's CHANGELOG. ([49586](https://github.com/WordPress/gutenberg/pull/49586)) +- Update documentation for `setGroupingBlockName`. ([49670](https://github.com/WordPress/gutenberg/pull/49670)) + +### Code Quality + +#### Components +- Ship TypeScript types. ([49229](https://github.com/WordPress/gutenberg/pull/49229)) +- Simplify `CustomGradientPicker`'s utility props. ([49588](https://github.com/WordPress/gutenberg/pull/49588)) + +#### Global Styles +- Refactor the effects panel as a generic UI Styles component. ([49571](https://github.com/WordPress/gutenberg/pull/49571)) +- Refactor the filters (duotone) panel as a generic UI Styles component. ([49577](https://github.com/WordPress/gutenberg/pull/49577)) + +#### Block Library +- Add automated tests for Nav block editable list view. ([49433](https://github.com/WordPress/gutenberg/pull/49433)) +- Cover: Avoid adding empty background image URL. ([49477](https://github.com/WordPress/gutenberg/pull/49477)) +- Remove editor block styles leftovers. ([49567](https://github.com/WordPress/gutenberg/pull/49567)) + +#### Site Editor +- Remove unused keyboard shortcut registration code. ([49553](https://github.com/WordPress/gutenberg/pull/49553)) +- Fix Site Editor isFeatureActive deprecation. ([49545](https://github.com/WordPress/gutenberg/pull/49545)) + +### Tools + +#### Testing +- Add tests for some of the drag and drop logic. ([49631](https://github.com/WordPress/gutenberg/pull/49631)) +- Adds to gitignore a playwright.config.override.ts entry. ([49329](https://github.com/WordPress/gutenberg/pull/49329)) +- Cover block: Fix failing test. ([49455](https://github.com/WordPress/gutenberg/pull/49455)) +- Workflows: Run PHP unit tests also against current WP - 1. ([46983](https://github.com/WordPress/gutenberg/pull/46983)) +- e2e: Fix flaky columns test. ([49676](https://github.com/WordPress/gutenberg/pull/49676)) +- test: Add Writing Flow tests. ([49352](https://github.com/WordPress/gutenberg/pull/49352)) + +#### Build Tooling +- Add workflow to check for expected @wordpress/components CHANGELOG updates. ([49443](https://github.com/WordPress/gutenberg/pull/49443)) +- Docgen: Replace line breaks with spaces and remove line-ending hyphens. ([49635](https://github.com/WordPress/gutenberg/pull/49635)) +- Convert wp data controls to typescript. ([49647](https://github.com/WordPress/gutenberg/pull/49647)) + +## First time contributors + +The following PRs were merged by first time contributors: + +- @cawa-93: List View: Allow horizontal scroll in panels in the post editor. ([49611](https://github.com/WordPress/gutenberg/pull/49611)) +- @DustyReagan: Cover Block: Fixed background causes image to be zoomed in on Safari browser on iPad. ([48981](https://github.com/WordPress/gutenberg/pull/48981)) +- @eduwass: Docs: Fix end-to-end command typos. ([49669](https://github.com/WordPress/gutenberg/pull/49669)) +- @HILAYTRIVEDI: A margin option is added to the Embeded preview block. ([39384](https://github.com/WordPress/gutenberg/pull/39384)) +- @mokagio: Fix Xcode spelling across the codebase. ([49625](https://github.com/WordPress/gutenberg/pull/49625)) +- @nefeline: Post excerpt > Ensure the postId from the block context is used to get_the_excerpt. ([49495](https://github.com/WordPress/gutenberg/pull/49495)) +- @thealexandrelara: Docs: Fix Create a Block Tutorial link on blocks package README.md. ([49562](https://github.com/WordPress/gutenberg/pull/49562)) + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @afercia @andrewserong @aristath @aurooba @brookewp @carolinan @cawa-93 @chad1008 @ciampo @dcalhoun @derekblank @draganescu @DustyReagan @eduwass @fluiddot @geriux @getdave @glendaviesnz @hellofromtonya @HILAYTRIVEDI @imanish003 @jameskoster @jasmussen @jhnstn @jorgefilipecosta @Mamaduka @mirka @mokagio @nefeline @noahtallen @ntsekouras @ockham @ramonjd @richtabor @ryanwelcher @SavPhill @scruffian @Soean @t-hamano @talldan @tellthemachines @thealexandrelara @tyxla @youknowriad + + + + += 15.5.1 = + +## Changelog + +### Bug Fixes + +#### Site Editor + +* Global styles: Fix error when updating padding input in global styles ([49465](https://github.com/WordPress/gutenberg/pull/49465)) + + += 15.5.0 = + +## Changelog + +### Enhancements + +#### Block Library +- Add Post Date block variation for Post Modified Date. ([49111](https://github.com/WordPress/gutenberg/pull/49111)) +- Add typography support to time to read block. ([49257](https://github.com/WordPress/gutenberg/pull/49257)) +- Allow the time to read block to be inserted multiple times. ([49253](https://github.com/WordPress/gutenberg/pull/49253)) +- Image Block: Don't render if there is no URL set. ([45220](https://github.com/WordPress/gutenberg/pull/45220)) +- Media & Text: Switch default alignment to `none`. ([48404](https://github.com/WordPress/gutenberg/pull/48404)) +- Update alternative text labels and help text. ([49416](https://github.com/WordPress/gutenberg/pull/49416)) +- Navigation Submenu: Refactor to use the block supports function. ([48936](https://github.com/WordPress/gutenberg/pull/48936)) +- Replace "Image size" with "Resolution" in image size controls. ([49112](https://github.com/WordPress/gutenberg/pull/49112)) +- Update Gallery block to use consistent 40px components. ([49101](https://github.com/WordPress/gutenberg/pull/49101)) + +#### Components +- FormTokenField: Add prop to remove bottom margin. ([48609](https://github.com/WordPress/gutenberg/pull/48609)) +- ImageSizeControl: Remove the "Image Dimensions" label. ([49414](https://github.com/WordPress/gutenberg/pull/49414)) +- ImageSizeControl: Use large 40px sizes. ([49113](https://github.com/WordPress/gutenberg/pull/49113)) +- Tooltip: Refine existing tests. ([48397](https://github.com/WordPress/gutenberg/pull/48397)) +- Update tests from `fireEvent` to `userEvent`. ([44952](https://github.com/WordPress/gutenberg/pull/44952)) +- CustomGradientPicker: Improve initial state UI. ([49146](https://github.com/WordPress/gutenberg/pull/49146)) +- Storybook: Declare stylesheet dependencies explicitly. ([49099](https://github.com/WordPress/gutenberg/pull/49099)) +- `AnglePickerControl`: Style to better fit in narrow contexts and improve RTL layout. ([49046](https://github.com/WordPress/gutenberg/pull/49046)) + +#### Design Tools +- Add spacing tools to time to read block. ([49392](https://github.com/WordPress/gutenberg/pull/49392)) +- Position Panel: Open by default if a position type is set. ([49151](https://github.com/WordPress/gutenberg/pull/49151)) +- Revert: Make sticky block action on template part block. ([49219](https://github.com/WordPress/gutenberg/pull/49219)) +- Sticky Position: Add a "Make sticky" action to the Template Part block. ([49085](https://github.com/WordPress/gutenberg/pull/49085)) + +#### Testing +- Adds focus management test for entity undo. ([49236](https://github.com/WordPress/gutenberg/pull/49236)) +- Adds navigation submenu tests. ([49351](https://github.com/WordPress/gutenberg/pull/49351)) +- Cover block: Add integration tests. ([45409](https://github.com/WordPress/gutenberg/pull/45409)) +- test: Expand mobile end-to-end test helpers. ([48978](https://github.com/WordPress/gutenberg/pull/48978)) + +#### Layout +- Make grid layout Group variation an experiment. ([49359](https://github.com/WordPress/gutenberg/pull/49359)) +- Try adding a grid layout type. ([49018](https://github.com/WordPress/gutenberg/pull/49018)) + +#### Icons +- Add unseen icon to library. ([49254](https://github.com/WordPress/gutenberg/pull/49254)) + +#### Plugins API +- Plugins: Refactor the 'PluginArea' component to use the sync store. ([49220](https://github.com/WordPress/gutenberg/pull/49220)) + +#### Global Styles +- Add: Story for the full global styles UI. ([49031](https://github.com/WordPress/gutenberg/pull/49031)) +- Caption element UI controls for color and typography. ([49141](https://github.com/WordPress/gutenberg/pull/49141)) + +#### Block Editor +- Rich text: Only consider a format active if active at every selected index. ([48789](https://github.com/WordPress/gutenberg/pull/48789)) +- Update border color on color panel items. ([42283](https://github.com/WordPress/gutenberg/pull/42283)) +- i18n: Add context to labels related to CSS position properties. ([49135](https://github.com/WordPress/gutenberg/pull/49135)) + +#### Site Editor +- Add: Patterns to the template start modal. ([47322](https://github.com/WordPress/gutenberg/pull/47322)) + +#### List View +- Allow the component to show a custom "more" menu. ([48097](https://github.com/WordPress/gutenberg/pull/48097)) + +#### Block API +- Block.json: Refactor and stabilize selectors API. ([46496](https://github.com/WordPress/gutenberg/pull/46496)) + + +### Bug Fixes + +#### Block Editor +- BlockHTML: Use correct type when setting 'html' state onBlur. ([49191](https://github.com/WordPress/gutenberg/pull/49191)) +- Duotone: Pass filters to the Post Editor. ([49239](https://github.com/WordPress/gutenberg/pull/49239)) +- Duotone: Use `WP_Theme_JSON_Resolver_Gutenberg` instead of `WP_Theme_JSON_Resolver`. ([49199](https://github.com/WordPress/gutenberg/pull/49199)) +- Fix typo (overriden -> overridden). ([48711](https://github.com/WordPress/gutenberg/pull/48711)) +- Writing flow: Prevent default browser behaviour on input when editable. ([49370](https://github.com/WordPress/gutenberg/pull/49370)) +- Fix `onHover` error on patterns tab in mobile. ([49450](https://github.com/WordPress/gutenberg/pull/49450)) + +#### General Interface +- Invalid Page address displayed when a future page is first scheduled. ([49092](https://github.com/WordPress/gutenberg/pull/49092)) + +#### Patterns +- Block patterns: Use `WP_Theme_JSON_Resolver_Gutenberg` instead of `WP_Theme_JSON_Resolver`. ([49197](https://github.com/WordPress/gutenberg/pull/49197)) + +#### Block Library +- Buttons: Disable edit as HTML support. ([49097](https://github.com/WordPress/gutenberg/pull/49097)) +- Fix center alignment on the dropdown in the categories block. ([44013](https://github.com/WordPress/gutenberg/pull/44013)) +- Fix sprintf() arguments. ([49439](https://github.com/WordPress/gutenberg/pull/49439)) +- Fix: Navigation block width constricted on large viewports. ([49302](https://github.com/WordPress/gutenberg/pull/49302)) +- Group Block: Allow blocks to be dragged onto it in its placeholder state. ([49361](https://github.com/WordPress/gutenberg/pull/49361)) +- Group: Fix the 'double div' deprecation 'templateLock ' attribute. ([49250](https://github.com/WordPress/gutenberg/pull/49250)) +- Group: Fix the 'templateLock' attribute type in deprecations. ([49205](https://github.com/WordPress/gutenberg/pull/49205)) +- Navigation: Fix bug in calculating the active menu item. ([49195](https://github.com/WordPress/gutenberg/pull/49195)) +- Post excerpt: Fix JavaScript error and other misc. bug fixes. ([48730](https://github.com/WordPress/gutenberg/pull/48730)) +- Query Loop: Constraint list item(`li`) styles to the direct children of the main list. ([49303](https://github.com/WordPress/gutenberg/pull/49303)) +- Query Loop: Show Featured Image placeholders in all posts. ([49301](https://github.com/WordPress/gutenberg/pull/49301)) +- Query Title: Changes filters for removing archive title prefixes. ([49306](https://github.com/WordPress/gutenberg/pull/49306)) +- Quote: Disable edit as HTML support. ([49426](https://github.com/WordPress/gutenberg/pull/49426)) +- Revert "Make sure the directly inserted block in the Nav block is a Page link". ([49126](https://github.com/WordPress/gutenberg/pull/49126)) +- Search block: Fix overflow width. ([49192](https://github.com/WordPress/gutenberg/pull/49192)) +- Changes image size description, as it causes confusion. ([48478](https://github.com/WordPress/gutenberg/pull/48478)) +- Post Excerpt Block: Fix unexpected commas in certain site languages. ([49123](https://github.com/WordPress/gutenberg/pull/49123)) +- columns block: Add support for templateLock attribute. ([49132](https://github.com/WordPress/gutenberg/pull/49132)) + +#### Components +- CircularOptionPicker: Force swatches to visually render on top of the rest of the component's content. ([49245](https://github.com/WordPress/gutenberg/pull/49245)) +- Fix TabPanel initial rendering. ([49368](https://github.com/WordPress/gutenberg/pull/49368)) +- Fix misaligned textarea input control. ([49116](https://github.com/WordPress/gutenberg/pull/49116)) +- LineHeightControl: Make spin buttons adjust from placeholder value. ([49150](https://github.com/WordPress/gutenberg/pull/49150)) +- ToolsPanel: Make menu item order consistent for SlotFill use cases. ([49222](https://github.com/WordPress/gutenberg/pull/49222)) + +#### Layout +- Fix unstableDisableLayoutClassNames in Group block. ([49385](https://github.com/WordPress/gutenberg/pull/49385)) +- Get orientation for Spacer block from parent layout. ([49322](https://github.com/WordPress/gutenberg/pull/49322)) +- Remove inner wrapper for grid Groups in classic themes. ([49387](https://github.com/WordPress/gutenberg/pull/49387)) + +#### Post Editor +- Fix breaking distraction free. ([49317](https://github.com/WordPress/gutenberg/pull/49317)) +- Post Lock: Fix the avatar position. ([49421](https://github.com/WordPress/gutenberg/pull/49421)) + +#### Site Editor +- Fix site editor redirection after creating new template or template part. ([49364](https://github.com/WordPress/gutenberg/pull/49364)) +- Show the created template title in success notice and not the slug. ([49366](https://github.com/WordPress/gutenberg/pull/49366)) +- Template parts: Use `WP_Theme_JSON_Resolver_Gutenberg` instead of `WP_Theme_JSON_Resolver`. ([49198](https://github.com/WordPress/gutenberg/pull/49198)) +- Fix typo in utils.js. ([49178](https://github.com/WordPress/gutenberg/pull/49178)) + +#### Media +- Image: Don't create an external image 'blob' when a user can't upload files. ([49300](https://github.com/WordPress/gutenberg/pull/49300)) +- MediaReplaceFlow: Check permissions before displaying the 'Media Library' menu item. ([49298](https://github.com/WordPress/gutenberg/pull/49298)) + +#### Global Styles +- Selectors API: Fix for global styles hook, style variations, and duotone. ([49393](https://github.com/WordPress/gutenberg/pull/49393)) +- Fix typo for the word `accross`. ([49295](https://github.com/WordPress/gutenberg/pull/49295)) +- Duotone: Limit SVG filter output to used filters. ([49103](https://github.com/WordPress/gutenberg/pull/49103)) +- Selectors API: Make duotone selectors fallback and be scoped. (https://github.com/WordPress/gutenberg/pull/49423) + +#### List View +- Show close button in List View on mobile. ([49200](https://github.com/WordPress/gutenberg/pull/49200)) +- Add private appender prop. ([49137](https://github.com/WordPress/gutenberg/pull/49137)) + +#### History +- Bring back revisions button for template parts. ([49163](https://github.com/WordPress/gutenberg/pull/49163)) + +#### Data Layer +- useSelect: Incrementally subscribe to stores when first selected from. ([47243](https://github.com/WordPress/gutenberg/pull/47243)) +- Data: Refuse to register an already registered store. ([49134](https://github.com/WordPress/gutenberg/pull/49134)) + +#### Tools +- babel-plugin-makepot: Fix non-existing translation handling. ([49431](https://github.com/WordPress/gutenberg/pull/49431)) + +#### Navigation +Fix fallback not rendering. ([49431](https://github.com/WordPress/gutenberg/pull/49576)) + +### Performance + +#### Post Editor +- Avoid UI shifting when selecting blocks. ([47177](https://github.com/WordPress/gutenberg/pull/47177)) +- Lodash: Remove `_.groupBy()` from `buildTermsTree()` in editor. ([49224](https://github.com/WordPress/gutenberg/pull/49224)) + +#### GitHub Actions +- Fix performance testing themes installation. ([49063](https://github.com/WordPress/gutenberg/pull/49063)) +- Speed up `npm ci` by caching `node_modules`. ([45932](https://github.com/WordPress/gutenberg/pull/45932)) + +#### Testings +- Fix running performance tests locally via CLI. ([49068](https://github.com/WordPress/gutenberg/pull/49068)) +- Refactor performance tests artifacts handling. ([48684](https://github.com/WordPress/gutenberg/pull/48684)) + +#### Babel Preset +- Polyfills: Exclude web.immediate. ([49234](https://github.com/WordPress/gutenberg/pull/49234)) + + +### Experiments + +#### Navigation Screen +- Remove 'edit-navigation' package leftovers. ([49183](https://github.com/WordPress/gutenberg/pull/49183)) + + +### Documentation + +- Add a missing filter documentation. ([44342](https://github.com/WordPress/gutenberg/pull/44342)) +- Add missing pseudo-selectors to theme.json schema and documentation. ([49202](https://github.com/WordPress/gutenberg/pull/49202)) +- Add readme.txt FAQ entry for filing security bugs. ([49148](https://github.com/WordPress/gutenberg/pull/49148)) +- Adds issue template for the plugin release process. ([49345](https://github.com/WordPress/gutenberg/pull/49345)) +- Adds message about usage of block-editor components. ([49400](https://github.com/WordPress/gutenberg/pull/49400)) +- Block Editor Handbook: Improved the readability and phrasing and corrected mistakes. ([48756](https://github.com/WordPress/gutenberg/pull/48756)) +- Docs: Fix incorrect import of apiFetch. ([49432](https://github.com/WordPress/gutenberg/pull/49432)) +- Docs: Put more structure to Architecture page. ([49184](https://github.com/WordPress/gutenberg/pull/49184)) +- Fix failing CI documentation check. ([49378](https://github.com/WordPress/gutenberg/pull/49378)) +- Fix/missing template header. ([49348](https://github.com/WordPress/gutenberg/pull/49348)) +- FlexItem: Fix typo line 19 of README. ([49284](https://github.com/WordPress/gutenberg/pull/49284)) +- Mention "testsPort" .wp-env.json setting. ([49388](https://github.com/WordPress/gutenberg/pull/49388)) +- Remove errant line break in versions-and-building documentation. ([49353](https://github.com/WordPress/gutenberg/pull/49353)) +- Theme JSON schema: Add defaultPresets property to shadow. ([49204](https://github.com/WordPress/gutenberg/pull/49204)) +- Theme JSON schema: Add position.sticky and dimensions.minHeight properties to settings. ([49335](https://github.com/WordPress/gutenberg/pull/49335)) +- Update @api-fetch README.md with query args. ([49318](https://github.com/WordPress/gutenberg/pull/49318)) +- Update README.md for @wordpress/element. createRoot not available until WordPress 6.2. ([49309](https://github.com/WordPress/gutenberg/pull/49309)) +- docs: Update end-to-end test device documentation. ([49441](https://github.com/WordPress/gutenberg/pull/49441)) + + +### Code Quality + +#### Components +- Animate: Refactor to TypeScript. ([49243](https://github.com/WordPress/gutenberg/pull/49243)) +- ColorPicker: TypeScript refactor. ([49214](https://github.com/WordPress/gutenberg/pull/49214)) +- CustomGradientPicker: Refactor to TypeScript. ([48929](https://github.com/WordPress/gutenberg/pull/48929)) +- DuotonePicker, DuotoneSwatch: Convert to TypeScript. ([49060](https://github.com/WordPress/gutenberg/pull/49060)) +- GradientPicker: Refactor to TypeScript. ([48316](https://github.com/WordPress/gutenberg/pull/48316)) + +#### Block Library +- Home Link Block: Remove leading spaces in class names. ([49397](https://github.com/WordPress/gutenberg/pull/49397)) +- Add column-gap and row-gap as allowed CSS properties for compatibility with WP 6.0. ([49118](https://github.com/WordPress/gutenberg/pull/49118)) + +#### Block Editor +- Duotone: Remove Safari rerender hack. ([49232](https://github.com/WordPress/gutenberg/pull/49232)) +- Replace regex with tag processor for duotone class render. ([49212](https://github.com/WordPress/gutenberg/pull/49212)) +- Use immutableSet utility to set style properties. ([49365](https://github.com/WordPress/gutenberg/pull/49365)) + +#### Plugins API +- Plugins: Add unit tests for the 'PluginArea' component. ([49138](https://github.com/WordPress/gutenberg/pull/49138)) + +#### Global Styles +- Extract a ColorPanel component as a reusable component between Global Styles and Block Inspector. ([48893](https://github.com/WordPress/gutenberg/pull/48893)) +- Lodash: Refactor away from `_.groupBy()` from `compileCSS()`. ([49227](https://github.com/WordPress/gutenberg/pull/49227)) + +#### Webfonts +- Tests: Fix typos in fonts API. ([47288](https://github.com/WordPress/gutenberg/pull/47288)) + +#### Compose +- Refactor useMediaQuery with useSyncExternalStore. ([48973](https://github.com/WordPress/gutenberg/pull/48973)) + +#### Patterns +- Add `gutenberg_get_remote_theme_patterns` function. ([49307](https://github.com/WordPress/gutenberg/pull/49307)) + +#### Element +- Element: Narrow `createInterpolateElement` param type. ([49182](https://github.com/WordPress/gutenberg/pull/49182)) + +### Tools + +#### Testing +- Fix flaky Site Editor title end-to-end tests. ([49203](https://github.com/WordPress/gutenberg/pull/49203)) +- Upgrade Playwright to 1.32. ([49296](https://github.com/WordPress/gutenberg/pull/49296)) + +#### Build Tooling +- Upgrade wp-prettier to 2.8.5. ([49258](https://github.com/WordPress/gutenberg/pull/49258)) + + +## First time contributors + +The following PRs were merged by first time contributors: + +- @enderandpeter: Mention "testsPort" .wp-env.json setting. ([49388](https://github.com/WordPress/gutenberg/pull/49388)) +- @hareesh-pillai: Site Editor: Fix typo in utils.js. ([49178](https://github.com/WordPress/gutenberg/pull/49178)) +- @helgatheviking: Update @api-fetch README.md with query args. ([49318](https://github.com/WordPress/gutenberg/pull/49318)) +- @janboddez: Fix sprintf() arguments. ([49439](https://github.com/WordPress/gutenberg/pull/49439)) +- @renintw: icons: Add unseen icon to library. ([49254](https://github.com/WordPress/gutenberg/pull/49254)) +- @richiecarey: FlexItem: Fix typo line 19 of README. ([49284](https://github.com/WordPress/gutenberg/pull/49284)) +- @sque89: columns block: Add support for templateLock attribute. ([49132](https://github.com/WordPress/gutenberg/pull/49132)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @ajlende @andrewserong @aristath @brookewp @carolinan @chad1008 @ciampo @corentin-gautier @dcalhoun @draganescu @ellatrix @enderandpeter @felixarntz @fluiddot @glendaviesnz @gziolo @hareesh-pillai @helgatheviking @janboddez @jeryj @jhnstn @johnbillion @jorgefilipecosta @jsnajdr @kevin940726 @madhusudhand @MaggieCabrera @Mamaduka @mburridge @mirka @ntsekouras @oandregal @renintw @richiecarey @richtabor @ryanwelcher @scruffian @sgomes @shreyasikhar @SiobhyB @Soean @sque89 @stokesman @t-hamano @talldan @tellthemachines @tomdevisser @tyxla @WunderBart @youknowriad @ZebulanStanphill + + += 15.4.0 = + +## Changelog + +### Enhancements + +#### Site Editor + +- Adjust whileHover effect to be a bit subtler and less pronounced. ([48928](https://github.com/WordPress/gutenberg/pull/48928)) +- Go direct to edit from manage all templates list. ([48764](https://github.com/WordPress/gutenberg/pull/48764)) +- Move "Add Template"'s descriptions to tooltips. ([48710](https://github.com/WordPress/gutenberg/pull/48710)) +- Add descriptions to all panels in the Site Editor's dark side. ([48739](https://github.com/WordPress/gutenberg/pull/48739)) +- Add hover animation to site editor canvas. ([48575](https://github.com/WordPress/gutenberg/pull/48575)) +- Fix non-us spelling in sidebar. ([48976](https://github.com/WordPress/gutenberg/pull/48976)) +- Prevent the saving button from showing when renaming templates. ([48399](https://github.com/WordPress/gutenberg/pull/48399)) +- Navigation Sidebar: Change the logic about which navigation gets selected for the sidebar. ([48689](https://github.com/WordPress/gutenberg/pull/48689)) +- Add "Added by" description to template part navigation sidebar. ([48732](https://github.com/WordPress/gutenberg/pull/48732)) +- Add border radius to off canvas navigation menu items. ([48798](https://github.com/WordPress/gutenberg/pull/48798)) +- Add page details when viewing a specific page. ([48650](https://github.com/WordPress/gutenberg/pull/48650)) +- Duotone: Limit SVG filter output to used filters. ([48995](https://github.com/WordPress/gutenberg/pull/48995)) +- Hide navigation screen in site editor. ([49043](https://github.com/WordPress/gutenberg/pull/49043)) + +#### Block Library + +- Open convert to links modal on select of a page item. ([48723](https://github.com/WordPress/gutenberg/pull/48723)) +- Post Featured Image: Remove 16:10. ([48969](https://github.com/WordPress/gutenberg/pull/48969)) +- Cover: Add constrained/flow layout. ([45326](https://github.com/WordPress/gutenberg/pull/45326)) +- Cover: Add text color block support. ([41572](https://github.com/WordPress/gutenberg/pull/41572)) + +#### Components + +- FontSizePicker: Allow custom units. ([48468](https://github.com/WordPress/gutenberg/pull/48468)) +- `Navigator`: Disable initial animation. ([49062](https://github.com/WordPress/gutenberg/pull/49062)) +- Try: Update Tertiary Button appearance. ([48888](https://github.com/WordPress/gutenberg/pull/48888)) +- FormTokenField: Hide suggestions list on blur event if input value is invalid. ([48785](https://github.com/WordPress/gutenberg/pull/48785)) + +#### Design Tools + +- Block Supports: Add text columns (column count) to typography block supports. ([33587](https://github.com/WordPress/gutenberg/pull/33587)) + +#### Global Styles + +- Move the global styles provider to the app level component. ([49011](https://github.com/WordPress/gutenberg/pull/49011)) +- Add support for `:link` and `:Any-link` in `theme.json`. ([48634](https://github.com/WordPress/gutenberg/pull/48634)) +- Add compound class to layout wrapper for global spacing styles. ([47952](https://github.com/WordPress/gutenberg/pull/47952)) + +#### Block API + +- Block Deprecations: Provide extra data for isEligible check. ([48815](https://github.com/WordPress/gutenberg/pull/48815)) + +#### Post Editor + +- Provide static native editor help article slugs. ([48802](https://github.com/WordPress/gutenberg/pull/48802)) +- Try getting Post Content layout on server before editor loads. ([45299](https://github.com/WordPress/gutenberg/pull/45299)) + +#### Packages + +- Introduce prependHTTPS URL util. ([47648](https://github.com/WordPress/gutenberg/pull/47648)) + +### Bug Fixes + +#### Block Library + +- Embed Block: Fix Aspect Ratio Classes #29641. ([41141](https://github.com/WordPress/gutenberg/pull/41141)) +- Ensure aspect ratio is applied when Post Featured Image block is linked. ([48495](https://github.com/WordPress/gutenberg/pull/48495)) +- Fix PostContent initial render by waiting for the canEdit request. ([48642](https://github.com/WordPress/gutenberg/pull/48642)) +- Fix classic menu fallback race condition. ([48811](https://github.com/WordPress/gutenberg/pull/48811)) +- Fix navigation block off-canvas appender for empty menus. ([48907](https://github.com/WordPress/gutenberg/pull/48907)) +- Fixes extra UI in navigation block inspector. ([48679](https://github.com/WordPress/gutenberg/pull/48679)) +- Import Classic Menu using the menu name as the block menu title. ([48771](https://github.com/WordPress/gutenberg/pull/48771)) +- Navigation Link: Remove color generation code. ([48927](https://github.com/WordPress/gutenberg/pull/48927)) +- Navigation: Fix missing state for MenuControls. ([48921](https://github.com/WordPress/gutenberg/pull/48921)) +- Update missing translation from label. ([48760](https://github.com/WordPress/gutenberg/pull/48760)) +- Widget Importer: Fix Widget Group block imports. ([48669](https://github.com/WordPress/gutenberg/pull/48669)) +- Query Loop: Show variant patterns even if there are no patterns for the Query Loop block. ([48793](https://github.com/WordPress/gutenberg/pull/48793)) +- Comments: Fix 'sprintf requires more than 1 params' error. ([49054](https://github.com/WordPress/gutenberg/pull/49054)) +- Adjust Post Featured Image PanelBody label to "Settings". ([49076](https://github.com/WordPress/gutenberg/pull/49076)) +- Add help text to Gallery Image Size control. ([49074](https://github.com/WordPress/gutenberg/pull/49074)) +- Comments Block (Legacy): Update missing translation. ([48820](https://github.com/WordPress/gutenberg/pull/48820)) +- I18n of created Navigation menu title. ([48773](https://github.com/WordPress/gutenberg/pull/48773)) +- Make sure the directly inserted block in the Nav block is a Page link. ([48740](https://github.com/WordPress/gutenberg/pull/48740)) +- Navigation Link: Don't remove 'block_core_navigation_link_build_css_colors'. ([49064](https://github.com/WordPress/gutenberg/pull/49064)) +- Navigation: Don't save the level of the link in an attribute. ([48219](https://github.com/WordPress/gutenberg/pull/48219)) +- Refactor away state in Nav menu selector. ([45464](https://github.com/WordPress/gutenberg/pull/45464)) +- Revert: Navigation: Always create a fallback menu. ([48602](https://github.com/WordPress/gutenberg/pull/48602)) +- Tweak Latest Posts block PanelBody labels. ([49079](https://github.com/WordPress/gutenberg/pull/49079)) +- Tweak label for Latest Posts excerpt control. ([49077](https://github.com/WordPress/gutenberg/pull/49077)) +- Page List Block: Show untitled pages on page list on the editor. ([48772](https://github.com/WordPress/gutenberg/pull/48772)) + +#### Site Editor + +- Don't offer Classic block as a recovery action when not registered. ([49051](https://github.com/WordPress/gutenberg/pull/49051)) +- Fix browser history when synchronising state with urls. ([48731](https://github.com/WordPress/gutenberg/pull/48731)) +- Fix lingering insertion point within template parts. ([48913](https://github.com/WordPress/gutenberg/pull/48913)) +- Fix template part actions in List View. ([48905](https://github.com/WordPress/gutenberg/pull/48905)) +- Fix text alignment in the Site Editor sidebar. ([48959](https://github.com/WordPress/gutenberg/pull/48959)) +- Fix typo in template parts description. ([48781](https://github.com/WordPress/gutenberg/pull/48781)) +- Fix browse mode descriptions margin. ([48778](https://github.com/WordPress/gutenberg/pull/48778)) +- Fix scrollbar in site editor. ([48822](https://github.com/WordPress/gutenberg/pull/48822)) +- Site Editor Navigation panel: Update appearance of non-link blocks. ([48933](https://github.com/WordPress/gutenberg/pull/48933)) +- Navigation sidebar shows a wrong submenu popover. ([48941](https://github.com/WordPress/gutenberg/pull/48941)) +- Show creation popover on empty page links in the navigation sidebar. ([48746](https://github.com/WordPress/gutenberg/pull/48746)) +- Site button metrics. ([48918](https://github.com/WordPress/gutenberg/pull/48918)) +- Remove actions from SidebarNavigationScreenWrapper. ([48935](https://github.com/WordPress/gutenberg/pull/48935)) +- Update template descriptions with more detail. ([48934](https://github.com/WordPress/gutenberg/pull/48934)) + +#### Global Styles + +- Fix typo: Use WP_Theme_JSON_Gutenberg instead of WP_Theme_JSON class name. ([48648](https://github.com/WordPress/gutenberg/pull/48648)) +- Fix: Crashes on getNodesWithSettings and getNodesWithStyles. ([49023](https://github.com/WordPress/gutenberg/pull/49023)) +- Fix: Global Styles crash in updateConfigWithSeparator when not block styles are passed. ([49045](https://github.com/WordPress/gutenberg/pull/49045)) +- Fix: Global Styles getNodesWithStyles expects an object with elements. ([49044](https://github.com/WordPress/gutenberg/pull/49044)) +- Fix: Global Styles getPresetsClasses crashes if no selector is passed. ([49024](https://github.com/WordPress/gutenberg/pull/49024)) +- Fix: Global styles forces a white background. ([49042](https://github.com/WordPress/gutenberg/pull/49042)) +- Style Book: Move iframe to root of content area to support styles that overflow block previews. ([48664](https://github.com/WordPress/gutenberg/pull/48664)) +- `WP_Theme_JSON`: Sync indirect properties changes from core. ([48646](https://github.com/WordPress/gutenberg/pull/48646)) + +#### Components + +- Fix HStack and VStack alignment prop. ([47914](https://github.com/WordPress/gutenberg/pull/47914)) +- ResizeTooltip: Use default.fontFamily on tooltip. ([48805](https://github.com/WordPress/gutenberg/pull/48805)) +- ResponsiveWrapper: Use aspect-ratio CSS prop and support SVG elements. ([48573](https://github.com/WordPress/gutenberg/pull/48573)) + +#### Accessibility + +- Make sure useFocusOnMount runs when all the children tabbable elements have mounted. ([42187](https://github.com/WordPress/gutenberg/pull/42187)) +- Manage selection on block sync. ([48979](https://github.com/WordPress/gutenberg/pull/48979)) + +#### Post Editor + +- Distraction Free Mode: Don't show the metaboxes. ([48947](https://github.com/WordPress/gutenberg/pull/48947)) +- Don't add Post Content layout styles to title in the post editor. ([48663](https://github.com/WordPress/gutenberg/pull/48663)) +- Fix animation and browser console error when returning from template edit mode. ([48930](https://github.com/WordPress/gutenberg/pull/48930)) + +#### Block Editor + +- LinkControl: Remove HTML from suggestion title before passing it to TextHighlight component. ([48685](https://github.com/WordPress/gutenberg/pull/48685)) +- Order initial block items in Navigation with PrivateInserter. ([48752](https://github.com/WordPress/gutenberg/pull/48752)) +- BlockInvalidWarning: Prefer `canInsertBlockType` and refactor to hooks. ([49052](https://github.com/WordPress/gutenberg/pull/49052)) +- Fix grouping actions in List View. ([48910](https://github.com/WordPress/gutenberg/pull/48910)) +- Fix typo in the media-categories component. ([49047](https://github.com/WordPress/gutenberg/pull/49047)) +- Custom link UI does appears outside canvas on the sidebar navigation. ([48633](https://github.com/WordPress/gutenberg/pull/48633)) +- Use proper color for block styles control. ([46684](https://github.com/WordPress/gutenberg/pull/46684)) +- Update Welcome Guide article links to avoid redirect. ([48582](https://github.com/WordPress/gutenberg/pull/48582)) +- Columns Block: Don't show the column count change UI when `templateLock` is `all`. ([48691](https://github.com/WordPress/gutenberg/pull/48691)) +- Remove border from quick inserter child elements. ([48794](https://github.com/WordPress/gutenberg/pull/48794)) + +#### Inspector Controls +- Fix settings tab active state border in block inspector. ([48945](https://github.com/WordPress/gutenberg/pull/48945)) + +#### Testing + +- Playwright Utils: Fix the 'publishPost' address locator. ([48729](https://github.com/WordPress/gutenberg/pull/48729)) + +#### CSS & Styling +- Fix duplication of block classname in feature selectors for style variations. ([48662](https://github.com/WordPress/gutenberg/pull/48662)) + +#### Experimental + +- Fix KSES filter for un-prettified filters. ([49004](https://github.com/WordPress/gutenberg/pull/49004)) + +#### Packages + +- Rich text: Fix range equality checks for Safari. ([48733](https://github.com/WordPress/gutenberg/pull/48733)) +- Preferences Modal: Fix double focus outline in tab item. ([48996](https://github.com/WordPress/gutenberg/pull/48996)) + +#### Tools + +- Scripts: Fix `render.php` isn't copied in Windows OS. ([48735](https://github.com/WordPress/gutenberg/pull/48735)) + +#### Mobile + +- Mobile - Fix parsing of CSS units for null matched values. ([48484](https://github.com/WordPress/gutenberg/pull/48484)) + +### Performance + +#### Block Editor + +- Rich text: useAnchor: Remove value dependency. ([48715](https://github.com/WordPress/gutenberg/pull/48715)) + +#### Post Editor + +- Lodash: Refactor away from `_.kebabCase()` in `EditorHelpTopics`. ([48776](https://github.com/WordPress/gutenberg/pull/48776)) +- Lodash: Refactor away from `edit-post` package. ([48786](https://github.com/WordPress/gutenberg/pull/48786)) + +#### Site Editor + +- Improve the Navigation panel's menu query. ([48908](https://github.com/WordPress/gutenberg/pull/48908)) +- Improve Site Editor performance tests. ([48138](https://github.com/WordPress/gutenberg/pull/48138)) + +#### Testing + +- Lodash: Remove from e2e-tests package. ([48775](https://github.com/WordPress/gutenberg/pull/48775)) + +#### Themes + +- Fix: Incorrect selector generated by `append_to_selector` method. ([48759](https://github.com/WordPress/gutenberg/pull/48759)) + +#### Block Library + +- Lodash: Remove `_.get()` from various blocks. ([48491](https://github.com/WordPress/gutenberg/pull/48491)) + +#### Data Layer + +- Lodash: Refactor away from `_.set()` in core-data. ([48784](https://github.com/WordPress/gutenberg/pull/48784)) + +#### GitHub Actions + +- Prefer committer over author date for perf results timestamp. ([48673](https://github.com/WordPress/gutenberg/pull/48673)) + +### Documentation + +- Add links to hook documentation in curation doc. ([48653](https://github.com/WordPress/gutenberg/pull/48653)) +- Add missing playwright end-to-end documentation to toc.json. ([48447](https://github.com/WordPress/gutenberg/pull/48447)) +- Adding examples of how to programmatically remove the panels in Document sidebar. ([48895](https://github.com/WordPress/gutenberg/pull/48895)) +- Adds link to post on the developer blog to the deprecation page. ([49069](https://github.com/WordPress/gutenberg/pull/49069)) +- Add position: Sticky to the Opt-in into UI controls appearanceTools section. ([48763](https://github.com/WordPress/gutenberg/pull/48763)) +- Fix broken Lerna documentation link. ([48890](https://github.com/WordPress/gutenberg/pull/48890)) +- Table of styles keys with since versions. ([48265](https://github.com/WordPress/gutenberg/pull/48265)) +- Fix URL mismatch. ([48931](https://github.com/WordPress/gutenberg/pull/48931)) +- Theme JSON schema: Add sticky position to settings, minHeight to styles. ([48948](https://github.com/WordPress/gutenberg/pull/48948)) +- Update the end-to-end tests documentation. ([48951](https://github.com/WordPress/gutenberg/pull/48951)) +- jest-preset-default: Update README to reflect current status. ([48925](https://github.com/WordPress/gutenberg/pull/48925)) + +### Code Quality + +#### Components + +- Autocomplete: Refactor to TypeScript. ([47751](https://github.com/WordPress/gutenberg/pull/47751)) +- Navigation: Refactor to TypeScript. ([48742](https://github.com/WordPress/gutenberg/pull/48742)) +- SelectControl: Improve prop types for single vs multiple selection. ([47390](https://github.com/WordPress/gutenberg/pull/47390)) +- `DimensionControl(Experimental)`: Refactor to TypeScript. ([47351](https://github.com/WordPress/gutenberg/pull/47351)) +- `Guide`: Refactor to TypeScript. ([47493](https://github.com/WordPress/gutenberg/pull/47493)) +- `Icon`: Refactor tests to TypeScript. ([49066](https://github.com/WordPress/gutenberg/pull/49066)) +- `PaletteEdit`: Refactor away from `lodash.kebabCase`. ([48637](https://github.com/WordPress/gutenberg/pull/48637)) +- `QueryControls`: Refactor away from `lodash.groupBy`. ([48779](https://github.com/WordPress/gutenberg/pull/48779)) +- components/utils/font: Refactor away from lodash `.get`. ([48629](https://github.com/WordPress/gutenberg/pull/48629)) +- remove lodash from `context/getStyledClassName`:. ([48688](https://github.com/WordPress/gutenberg/pull/48688)) +- withSpokenMessages: Change js files to typescript. ([48163](https://github.com/WordPress/gutenberg/pull/48163)) + +#### Block Library + +- Add Nav block files to those triggering error for exhaustive deps. ([48821](https://github.com/WordPress/gutenberg/pull/48821)) +- Fix Nav block exhaustive deps warnings. ([48680](https://github.com/WordPress/gutenberg/pull/48680)) +- Media Text: Refactored constants to it's designated file. ([48480](https://github.com/WordPress/gutenberg/pull/48480)) +- Navigation: Simplify the method for finding the fallback menu. ([48916](https://github.com/WordPress/gutenberg/pull/48916)) +- Duotone.php code cleanup. ([48607](https://github.com/WordPress/gutenberg/pull/48607)) +- Revert "Duotone: Limit SVG filter output to used filters". ([49102](https://github.com/WordPress/gutenberg/pull/49102)) + +#### Block Editor + +- Inserter: Remove outer scope values dependencies. ([48961](https://github.com/WordPress/gutenberg/pull/48961)) +- Inserter: Remove unnecessary dependency 'delayedFilterValue'. ([48960](https://github.com/WordPress/gutenberg/pull/48960)) +- Remove unused CSS from LinkControl Apply button. ([48431](https://github.com/WordPress/gutenberg/pull/48431)) +- Custom Classname block support: Update code comments to remove reference to anchor id. ([48709](https://github.com/WordPress/gutenberg/pull/48709)) +- List View: Remove unused selector from the 'useBlockSelection' hook. ([48984](https://github.com/WordPress/gutenberg/pull/48984)) +- Renames parent selection boolean param and improves documentation. ([48677](https://github.com/WordPress/gutenberg/pull/48677)) +- Tests: Cleanup unnecessary jest timers setup. ([49030](https://github.com/WordPress/gutenberg/pull/49030)) +- Avoid declaring a function inside another function. ([49049](https://github.com/WordPress/gutenberg/pull/49049)) + +#### Global Styles + +- Theme JSON: Clarify status of fixed position opt-in in appearance tools. ([48660](https://github.com/WordPress/gutenberg/pull/48660)) +- Extract a BorderPanel component as a reusable component between Global Styles and Block Inspector. ([48636](https://github.com/WordPress/gutenberg/pull/48636)) + +#### Data Layer + +- Data: Use real timers for private APIs tests. ([49029](https://github.com/WordPress/gutenberg/pull/49029)) + +#### Packages + +- Preferences: Remove `types` field from `package.json`. ([49053](https://github.com/WordPress/gutenberg/pull/49053)) +- Upgrade `typescript` to 4.9.5. ([48299](https://github.com/WordPress/gutenberg/pull/48299)) +- Compose: Remove useAsyncList from mobile exports. ([48241](https://github.com/WordPress/gutenberg/pull/48241)) +- Animation: Refactor to TypeScript. ([47042](https://github.com/WordPress/gutenberg/pull/47042)) +- PanelBody: Convert to TypeScript. ([47702](https://github.com/WordPress/gutenberg/pull/47702)) +- Refactor ToolbarContext to TS. ([49002](https://github.com/WordPress/gutenberg/pull/49002)) +- Refactor/toolbar button component to typescript. ([47750](https://github.com/WordPress/gutenberg/pull/47750)) +- `PaletteEdit`: Convert to TypeScript. ([47764](https://github.com/WordPress/gutenberg/pull/47764)) +- navigateRegions: Convert to TypeScript. ([48632](https://github.com/WordPress/gutenberg/pull/48632)) +- withFallbackStyles: Convert to TypeScript. ([48720](https://github.com/WordPress/gutenberg/pull/48720)) +- withFilters: Convert to TypeScript. ([48721](https://github.com/WordPress/gutenberg/pull/48721)) +- withFocusReturn: Convert to TypeScript. ([48748](https://github.com/WordPress/gutenberg/pull/48748)) +- withNotices: Convert to TypeScript. ([49088](https://github.com/WordPress/gutenberg/pull/49088)) +- Packages: Remove completely two deprecated webpack plugins. ([48770](https://github.com/WordPress/gutenberg/pull/48770)) + +### Tools + +- Env: Fix typo / grammar README.md. ([48952](https://github.com/WordPress/gutenberg/pull/48952)) +- ci: Add Rich Text code owner. ([48727](https://github.com/WordPress/gutenberg/pull/48727)) + +#### Testing + +- Add `pageUtils.pressKeys` to playwright utils. ([49009](https://github.com/WordPress/gutenberg/pull/49009)) +- Add artifacts upload for the performance tests. ([48243](https://github.com/WordPress/gutenberg/pull/48243)) +- Fix flaky block hierarchy navigation test by better inserter selection. ([48780](https://github.com/WordPress/gutenberg/pull/48780)) +- Migrate multi-block selection end-to-end tests to Playwright. ([48035](https://github.com/WordPress/gutenberg/pull/48035)) +- Navigation block end-to-end tests: Default to my most recently created menu. ([48132](https://github.com/WordPress/gutenberg/pull/48132)) +- Upgrade Jest from 27 to 29.5.0. ([47388](https://github.com/WordPress/gutenberg/pull/47388)) +- Duotone: Style Engine: Add unit test and associated refactoring. ([49033](https://github.com/WordPress/gutenberg/pull/49033)) +- Query Block: Add tests for `getValueFromObjectPath()` util. ([48956](https://github.com/WordPress/gutenberg/pull/48956)) + +## First time contributors + +The following PRs were merged by first time contributors: + +- @anver: Env: Fix typo / grammar README.md. ([48952](https://github.com/WordPress/gutenberg/pull/48952)) +- @bhavz-10: withSpokenMessages: Change js files to typescript. ([48163](https://github.com/WordPress/gutenberg/pull/48163)) +- @krishneup: Update missing translation from label. ([48760](https://github.com/WordPress/gutenberg/pull/48760)) +- @mike-day: Refactor/toolbar button component to typescript. ([47750](https://github.com/WordPress/gutenberg/pull/47750)) +- @shvlv: FormTokenField: Hide suggestions list on blur event if input value is invalid. ([48785](https://github.com/WordPress/gutenberg/pull/48785)) +- @TylerB24890: Embed Block: Fix Aspect Ratio Classes #29641. ([41141](https://github.com/WordPress/gutenberg/pull/41141)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @afercia @ajlende @andrewserong @anver @aristath @bhavz-10 @brookewp @chad1008 @ciampo @DaisyOlsen @dcalhoun @draganescu @ellatrix @fabiankaegy @felixarntz @flootr @fluiddot @geriux @getdave @glendaviesnz @gvgvgvijayan @gziolo @hideokamoto @jameskoster @jasmussen @jeryj @jorgefilipecosta @jsnajdr @kevin940726 @kienstra @krishneup @MaggieCabrera @Mamaduka @mburridge @mike-day @mirka @mtias @ndiego @ntsekouras @oandregal @Rahmon @richtabor @ryanwelcher @SavPhill @scruffian @shvlv @SiobhyB @swissspidy @t-hamano @talldan @tellthemachines @tomdevisser @TylerB24890 @tyxla @WunderBart @youknowriad + + += 15.3.1 = + + + +## Changelog + +### Enhancements + +#### Site Editor +- Refactor the site editor URLs for better backward compatibility. ([48063](https://github.com/WordPress/gutenberg/pull/48063)) +- Remove TemplateAreas from template details. ([48490](https://github.com/WordPress/gutenberg/pull/48490)) +- Update the edit button. ([48584](https://github.com/WordPress/gutenberg/pull/48584)) +- Change "Browse all templates" to "Manage all templates" in template details popover. ([48496](https://github.com/WordPress/gutenberg/pull/48496)) +- Match color of the focus template view with site dark material. ([48430](https://github.com/WordPress/gutenberg/pull/48430)) +- Polish add template modal style. ([48445](https://github.com/WordPress/gutenberg/pull/48445)) +- Site Editor Sidebar: Add line-height for template/parts name and update width for edit button. ([48160](https://github.com/WordPress/gutenberg/pull/48160)) +- Extract a DimensionsPanel component as a reusable component between Global Styles and Block Inspector. ([48070](https://github.com/WordPress/gutenberg/pull/48070)) +- Remove copy for managing a block's style variations. ([48367](https://github.com/WordPress/gutenberg/pull/48367)) + +#### Block Editor +- Apply keyboard shortcut conversions between paragraphs and headings to all editor instances. ([47972](https://github.com/WordPress/gutenberg/pull/47972)) +- Create automatic change higher order reducer. ([48312](https://github.com/WordPress/gutenberg/pull/48312)) +- [Inserter]: Preload media categories empty check - client side. ([47503](https://github.com/WordPress/gutenberg/pull/47503)) + +#### Post Editor +- Revert iframed editor for WP core only. ([48076](https://github.com/WordPress/gutenberg/pull/48076)) +- PageAttributesCheck: Return boolean value directly from the selector. ([48336](https://github.com/WordPress/gutenberg/pull/48336)) +- Apply busy status to the publish button in progress and unify button width. ([48444](https://github.com/WordPress/gutenberg/pull/48444)) + +#### Block Library +- Add all allowed innerblocks to the inspector animation experiment. ([47834](https://github.com/WordPress/gutenberg/pull/47834)) +- Make the Site Logo block placeholder state smaller. ([48218](https://github.com/WordPress/gutenberg/pull/48218)) +- ToggleControl: Remove margin overrides and add opt-in prop. ([47866](https://github.com/WordPress/gutenberg/pull/47866)) +- Post Terms block: Add transforms for variations. ([48328](https://github.com/WordPress/gutenberg/pull/48328)) +- List block: Add Firefox end-to-end tests. ([48271](https://github.com/WordPress/gutenberg/pull/48271)) +- Navigation List View: Add block movers to the more menu. ([48099](https://github.com/WordPress/gutenberg/pull/48099)) +- Navigation: Always create a fallback menu. ([47684](https://github.com/WordPress/gutenberg/pull/47684)) +- Navigation: Don't create a fallback navigation menu if there are inner blocks. ([48585](https://github.com/WordPress/gutenberg/pull/48585)) +- Navigation: Don't create duplicate navigation menus. ([48599](https://github.com/WordPress/gutenberg/pull/48599)) +- Navigation: Update the dependencies for the useEffect that handles notifications. ([48066](https://github.com/WordPress/gutenberg/pull/48066)) +- Navigation: Wrap the dependent functions in useCallback. ([48195](https://github.com/WordPress/gutenberg/pull/48195)) +- Page List: Move the modal to its own file. ([47922](https://github.com/WordPress/gutenberg/pull/47922)) +- [New Block] Add post time to read block. ([43403](https://github.com/WordPress/gutenberg/pull/43403)) +- Classic Block: Replace the deprecated `isPrimary` prop with `variant`. ([48230](https://github.com/WordPress/gutenberg/pull/48230)) +- Refactor core blocks to use HTML Tag Processor. ([43178](https://github.com/WordPress/gutenberg/pull/43178)) + +#### Block API +- HTML Tag Processor: Add WP 6.3 compat layer. ([47933](https://github.com/WordPress/gutenberg/pull/47933)) +- Tag Processor: Add bookmark invalidation logic. ([47559](https://github.com/WordPress/gutenberg/pull/47559)) + +#### Design Tools +- Duotone: Use the style engine to generate CSS for Duotone. ([48281](https://github.com/WordPress/gutenberg/pull/48281)) +- Duotone: Add Global Styles controls for blocks that support duotone. ([48255](https://github.com/WordPress/gutenberg/pull/48255)) +- Use Duotone presets in block duotone attributes. ([48318](https://github.com/WordPress/gutenberg/pull/48318)) +- Duotone: Use CSS variables instead of slugs in block attributes. ([48426](https://github.com/WordPress/gutenberg/pull/48426)) +- Border Panel: Add missing dep for `onBorderChange` callback. ([48010](https://github.com/WordPress/gutenberg/pull/48010)) +- Disable layout toolbar controls for content locked blocks. ([47939](https://github.com/WordPress/gutenberg/pull/47939)) +- SpacingSizesControl: Fix white dot on thumb. ([48574](https://github.com/WordPress/gutenberg/pull/48574)) + +#### List View +- Scroll selected block into view when single block selection changes. ([46895](https://github.com/WordPress/gutenberg/pull/46895)) + +#### Components +- TabPanel: Refactor unit tests in prep for controlled component updates. ([48086](https://github.com/WordPress/gutenberg/pull/48086)) +- Change higher order `with-constraint-tabbing` from `.js` to `.tsx`. ([48162](https://github.com/WordPress/gutenberg/pull/48162)) +- CircularOptionPicker: Refactor to TypeScript. ([47937](https://github.com/WordPress/gutenberg/pull/47937)) +- Extract delete handler to hook for RichText. ([48273](https://github.com/WordPress/gutenberg/pull/48273)) +- ToolsPanel: Remove unnecessary dep from resetAll callback. ([48011](https://github.com/WordPress/gutenberg/pull/48011)) +- Link Control - Add support for text only labels. ([47930](https://github.com/WordPress/gutenberg/pull/47930)) +- Pin `floating-ui/react-dom` version. ([48402](https://github.com/WordPress/gutenberg/pull/48402)) +- Use React 18 rendering for import dropdown. ([48244](https://github.com/WordPress/gutenberg/pull/48244)) + +### Bug Fixes + +#### Site Editor +- Fix Site Editor perf tests. ([48240](https://github.com/WordPress/gutenberg/pull/48240)) +- Fix routing for Classic themes using block-based template parts. ([48343](https://github.com/WordPress/gutenberg/pull/48343)) +- Fix the 'Browse all' link in the template details modal. ([48301](https://github.com/WordPress/gutenberg/pull/48301)) +- Fix the site editor home page loading when installed in a subdirectory. ([48363](https://github.com/WordPress/gutenberg/pull/48363)) +- Fix the tooltip and shortcut for the global save button. ([48282](https://github.com/WordPress/gutenberg/pull/48282)) +- Prevent distracting focused back button on site editor load. ([48472](https://github.com/WordPress/gutenberg/pull/48472)) +- Sort templates and template parts by slug. ([48473](https://github.com/WordPress/gutenberg/pull/48473)) +- Specify focus state color for template navigation button. ([48134](https://github.com/WordPress/gutenberg/pull/48134)) +- [Site Editor]: Set `html` block as freeform fallback block. ([48129](https://github.com/WordPress/gutenberg/pull/48129)) +- Remove broken site editor redirect. ([48283](https://github.com/WordPress/gutenberg/pull/48283)) +- Fix: Inserter does not appear on sidebar navigation. ([48623](https://github.com/WordPress/gutenberg/pull/48623)) +- Fix: Make navigation page list load its items on navigation sidebar. ([47853](https://github.com/WordPress/gutenberg/pull/47853)) +- Revert Site Tagline placeholder attribute, move example to block.json. ([48383](https://github.com/WordPress/gutenberg/pull/48383)) + +#### Block Editor +- MediaReplaceFlow: Fix styling for LinkControl. ([47949](https://github.com/WordPress/gutenberg/pull/47949)) +- Fix "Reset All" button in the typography panel of the block inspector. ([48123](https://github.com/WordPress/gutenberg/pull/48123)) +- [Inserter - Media tab]: Upload Openverse images when inserted. ([48501](https://github.com/WordPress/gutenberg/pull/48501)) +- Block alignment controls: Don't remount the block when alignments change. ([48209](https://github.com/WordPress/gutenberg/pull/48209)) +- Pin `valtio` version. ([48237](https://github.com/WordPress/gutenberg/pull/48237)) +- Fix block style preview flickering when a 'style' is focused. ([48418](https://github.com/WordPress/gutenberg/pull/48418)) +- Enable access to block settings within UBE. ([48435](https://github.com/WordPress/gutenberg/pull/48435)) +- Fix: Content only CPT template locking. ([48434](https://github.com/WordPress/gutenberg/pull/48434)) +- Firefox: Fix input rules (React async state issue). ([48210](https://github.com/WordPress/gutenberg/pull/48210)) +- Fix: Multiple overwrites on rest_controller_class for wp_template/wp_template_part. ([48078](https://github.com/WordPress/gutenberg/pull/48078)) +- Select blocks in `outline` list. ([48118](https://github.com/WordPress/gutenberg/pull/48118)) +- Only add layout classes to inner wrapper if block is a container. ([48611](https://github.com/WordPress/gutenberg/pull/48611)) + +#### Widgets Editor +- Widget Editor: Fix a problem with 'Move to Widget Area' button not working. ([48233](https://github.com/WordPress/gutenberg/pull/48233)) + +#### Block Library +- Add support for orientation-based block movers to core/social-links. ([48452](https://github.com/WordPress/gutenberg/pull/48452)) +- Fix Post Excerpt: Read more link is always on new line in the editor. ([47772](https://github.com/WordPress/gutenberg/pull/47772)) +- Fix: OffCanvasEditor does not inserts submenu on collapsed items. ([48214](https://github.com/WordPress/gutenberg/pull/48214)) +- List Item: Avoid an error when the 'onReplace' prop is undefined. ([48639](https://github.com/WordPress/gutenberg/pull/48639)) +- Add missing code on the fix page list loading PR. ([48621](https://github.com/WordPress/gutenberg/pull/48621)) +- Prevent text decoration from showing up in all blocks. ([48117](https://github.com/WordPress/gutenberg/pull/48117)) +- Widget Importer: Don't render controls when there's nothing to import. ([48396](https://github.com/WordPress/gutenberg/pull/48396)) +- [Query Loop] Fix top border in pattern selection modal. ([48303](https://github.com/WordPress/gutenberg/pull/48303)) +- [Query Loop] Sync `gutenberg_build_query_vars_from_query_block` with core. ([48640](https://github.com/WordPress/gutenberg/pull/48640)) +- Avatar: Clean up border application in editor. ([48024](https://github.com/WordPress/gutenberg/pull/48024)) + +#### Global Styles +- Fix for `WP_Theme_JSON_Resolver_Gutenberg::Get_merged_data`. ([48644](https://github.com/WordPress/gutenberg/pull/48644)) +- Display preset names via the tooltip. ([48304](https://github.com/WordPress/gutenberg/pull/48304)) +- Fix typo in valid settings for fluid typography. ([48605](https://github.com/WordPress/gutenberg/pull/48605)) +- Prevent the global styles previews thumbnails from resizing on load. ([48474](https://github.com/WordPress/gutenberg/pull/48474)) +- Site Tagline: Add example so that it will display in style book. ([48300](https://github.com/WordPress/gutenberg/pull/48300)) +- Fix wrong property name letterSpacing to lineHeight. ([48091](https://github.com/WordPress/gutenberg/pull/48091)) +- Update code position for constants and methods in `WP_Theme_JSON_Gutenberg` class. ([48631](https://github.com/WordPress/gutenberg/pull/48631)) +- `WP_Theme_JSON_Gutenberg`: Update comments to be aligned with core, so backports are easier. ([48624](https://github.com/WordPress/gutenberg/pull/48624)) + +### Accessibility +- Autocomplete: Duplicate list within iframe for non visual users. ([47907](https://github.com/WordPress/gutenberg/pull/47907)) +- Fix site editor switch mode message. ([48136](https://github.com/WordPress/gutenberg/pull/48136)) +- Fix the Publish region position and focus style. ([48102](https://github.com/WordPress/gutenberg/pull/48102)) +- Focus 1st parent block on block remove, if no previous block is available. ([48204](https://github.com/WordPress/gutenberg/pull/48204)) +- List view: Allow selected block to override roving tabindex. ([48339](https://github.com/WordPress/gutenberg/pull/48339)) +- Style Book: Allow button text labels for style book icon. ([48088](https://github.com/WordPress/gutenberg/pull/48088)) +- Style Book: Focus the Style Book when opened, and enable ESCAPE key to close. ([48151](https://github.com/WordPress/gutenberg/pull/48151)) +- Try to fix a11y test flakiness. ([48236](https://github.com/WordPress/gutenberg/pull/48236)) +- Make the template customized info accessible. ([48159](https://github.com/WordPress/gutenberg/pull/48159)) + + +### Performance + +#### Block Editor +- Block Editor: Improve empty `getBlockParents()` perf. ([48242](https://github.com/WordPress/gutenberg/pull/48242)) +- Fix perf regression in duotone hooks. ([48401](https://github.com/WordPress/gutenberg/pull/48401)) +- Writing flow: Avoid recalc style on every selection change. ([48409](https://github.com/WordPress/gutenberg/pull/48409)) +- useAsyncList: Flush state updates when processing queue. ([48238](https://github.com/WordPress/gutenberg/pull/48238)) + +#### Components +- Autocomplete: Reduce work before finding trigger. ([48327](https://github.com/WordPress/gutenberg/pull/48327)) +- Disable lazy term meta loading render_block_core_template_part. ([48000](https://github.com/WordPress/gutenberg/pull/48000)) +- Rich text: Only selectively handle keyup/pointerup. ([48385](https://github.com/WordPress/gutenberg/pull/48385)) +- Rich text: Remove unnecessary handleSelectionChange call. ([48373](https://github.com/WordPress/gutenberg/pull/48373)) +- Rich text: Try removing store change on focus. ([48342](https://github.com/WordPress/gutenberg/pull/48342)) +- Autocomplete: Avoid calling setState on input. ([48565](https://github.com/WordPress/gutenberg/pull/48565)) +- Autocomplete: Don't change state on every keystroke. ([48485](https://github.com/WordPress/gutenberg/pull/48485)) + +### Code Quality +- Tag Processor: Make `$html` field `protected`. ([48681](https://github.com/WordPress/gutenberg/pull/48681)) +- WP_HTML_Tag_Processor: Support tag closer bookmarks. ([48692](https://github.com/WordPress/gutenberg/pull/48692)) +- Fixed incorrect type annotations in @wordpress/data, part 2. ([48221](https://github.com/WordPress/gutenberg/pull/48221)) +- Lodash: Refactor away from `_.merge()`. ([48239](https://github.com/WordPress/gutenberg/pull/48239)) +- Lodash: Remove from integration tests. ([48122](https://github.com/WordPress/gutenberg/pull/48122)) +- Lodash: Remove remaining `_.get()` from `core-data`. ([48310](https://github.com/WordPress/gutenberg/pull/48310)) +- Lodash: Remove `_.get()` for post type usages. ([48121](https://github.com/WordPress/gutenberg/pull/48121)) +- Lodash: Remove remaining `_.get()` from `editor`. ([48314](https://github.com/WordPress/gutenberg/pull/48314)) +- Lodash: Remove some `_.get()` from editor. ([48104](https://github.com/WordPress/gutenberg/pull/48104)) +- Lodash: Refactor context system provider away from `_.merge()`. ([48036](https://github.com/WordPress/gutenberg/pull/48036)) +- Lodash: Remove `_.merge()` from `getMappedColumnWidths()`. ([48032](https://github.com/WordPress/gutenberg/pull/48032)) +- Lodash: Remove some `_.get()` from Gallery block. ([48488](https://github.com/WordPress/gutenberg/pull/48488)) +- Lodash: Remove some `_.get()` from Image block. ([48489](https://github.com/WordPress/gutenberg/pull/48489)) +- Playwright: Fix request utils for non Docker envs. ([48206](https://github.com/WordPress/gutenberg/pull/48206)) +- [Private APIs] Only prevent module re-registration if IS_WORDPRESS_CORE. ([48352](https://github.com/WordPress/gutenberg/pull/48352)) +- Add a manual performance job that we can trigger from Github UI. ([48302](https://github.com/WordPress/gutenberg/pull/48302)) +- Track new front-end metric: LCP-TTFB. ([48288](https://github.com/WordPress/gutenberg/pull/48288)) + +### Documentation +- Clarify backport status of `appearance-tools` theme support. ([48622](https://github.com/WordPress/gutenberg/pull/48622)) +- Docs: Clarify release process for major WP Beta 1. ([48248](https://github.com/WordPress/gutenberg/pull/48248)) +- Docs: Markdown fix for wp_enqueue_script usage. ([48212](https://github.com/WordPress/gutenberg/pull/48212)) +- Fix `e2e-test-utils-playwright`'s jsdoc and types. ([48266](https://github.com/WordPress/gutenberg/pull/48266)) +- Fix incorrect css property in editor-color-palette documentation. ([48333](https://github.com/WordPress/gutenberg/pull/48333)) +- Proactively update versions in WordPress ahead of 6.2. ([48145](https://github.com/WordPress/gutenberg/pull/48145)) +- Update curation document to include theme.json filters. ([48576](https://github.com/WordPress/gutenberg/pull/48576)) +- Updates to the curating the editor experience to include 6.1 & 6.2 items. ([48294](https://github.com/WordPress/gutenberg/pull/48294)) + +### Tools +#### Testing +- Migrate `switch-to-draft` to Playwright. ([48120](https://github.com/WordPress/gutenberg/pull/48120)) +- VizReg end-to-end tests: Programmatically test all combinations of a given list of props/values. ([48260](https://github.com/WordPress/gutenberg/pull/48260)) +- Update end-to-end test snapshots to Jest 29 default. ([48626](https://github.com/WordPress/gutenberg/pull/48626)) +- Update snapshot format to Jest 29 default. ([48366](https://github.com/WordPress/gutenberg/pull/48366)) +- Move react-native-editor parser tests to a test directory. ([48615](https://github.com/WordPress/gutenberg/pull/48615)) +- `ToolsPanel`: Refactor unit tests to TypeScript. ([48275](https://github.com/WordPress/gutenberg/pull/48275)) +- Migrate block deletion end-to-end tests to Playwright. ([48012](https://github.com/WordPress/gutenberg/pull/48012)) +- Migrate list view end-to-end tests to Playwright. ([47919](https://github.com/WordPress/gutenberg/pull/47919)) +- Navigation block: End-to-end code quality fixes. ([48071](https://github.com/WordPress/gutenberg/pull/48071)) +- Remove old end-to-end tests for the navigation block. ([48126](https://github.com/WordPress/gutenberg/pull/48126)) +- Update assertion Autocomplete end-to-end tests. ([48344](https://github.com/WordPress/gutenberg/pull/48344)) +- [Automated Testing]: Fix wrong button fixture. ([48305](https://github.com/WordPress/gutenberg/pull/48305)) +- Add typescript-eslint rules with type informations to end-to-end tests. ([48267](https://github.com/WordPress/gutenberg/pull/48267)) +- Fix snapshots for Spacer mobile unit tests. ([48406](https://github.com/WordPress/gutenberg/pull/48406)) +- Add command to run performance tests in debug mode. ([48614](https://github.com/WordPress/gutenberg/pull/48614)) +- Make the performance tests more stable. ([48094](https://github.com/WordPress/gutenberg/pull/48094)) + +## First time contributors + +The following PRs were merged by first time contributors: + +- @abhi3315: Link Control - Add support for text only labels. ([47930](https://github.com/WordPress/gutenberg/pull/47930)) +- @hbhalodia: Update: Change higher order `with-constraint-tabbing` from `.js` to `.tsx`. ([48162](https://github.com/WordPress/gutenberg/pull/48162)) +- @sboerrigter: Fix incorrect css property in editor-color-palette documentation. ([48333](https://github.com/WordPress/gutenberg/pull/48333)) +- @shreyasikhar: Site Editor Sidebar: Add line-height for template/parts name and update width for edit button. ([48160](https://github.com/WordPress/gutenberg/pull/48160)) +- @suvrodattamitu: Style Engine: Fix wrong property name letterSpacing to lineHeight. ([48091](https://github.com/WordPress/gutenberg/pull/48091)) +- @tomdevisser: Remove copy for managing a block's style variations. ([48367](https://github.com/WordPress/gutenberg/pull/48367)) + + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @abhi3315 @adamziel @afercia @alexstine @andrewserong @annezazu @brookewp @carolinan @chad1008 @ciampo @dcalhoun @draganescu @ellatrix @fluiddot @geriux @getdave @gvgvgvijayan @gziolo @hbhalodia @jorgefilipecosta @jsnajdr @kevin940726 @MaggieCabrera @Mamaduka @marekdedic @ndiego @ntsekouras @oandregal @ockham @richtabor @sboerrigter @scruffian @shreyasikhar @spacedmonkey @suvrodattamitu @t-hamano @tellthemachines @tomdevisser @tyxla @WunderBart @youknowriad + + += 15.3.0 = + + + +## Changelog + +### Enhancements + +#### Site Editor +- Refactor the site editor URLs for better backward compatibility. ([48063](https://github.com/WordPress/gutenberg/pull/48063)) +- Remove TemplateAreas from template details. ([48490](https://github.com/WordPress/gutenberg/pull/48490)) +- Update the edit button. ([48584](https://github.com/WordPress/gutenberg/pull/48584)) +- Change "Browse all templates" to "Manage all templates" in template details popover. ([48496](https://github.com/WordPress/gutenberg/pull/48496)) +- Match color of the focus template view with site dark material. ([48430](https://github.com/WordPress/gutenberg/pull/48430)) +- Polish add template modal style. ([48445](https://github.com/WordPress/gutenberg/pull/48445)) +- Site Editor Sidebar: Add line-height for template/parts name and update width for edit button. ([48160](https://github.com/WordPress/gutenberg/pull/48160)) +- Extract a DimensionsPanel component as a reusable component between Global Styles and Block Inspector. ([48070](https://github.com/WordPress/gutenberg/pull/48070)) +- Remove copy for managing a block's style variations. ([48367](https://github.com/WordPress/gutenberg/pull/48367)) + +#### Block Editor +- Apply keyboard shortcut conversions between paragraphs and headings to all editor instances. ([47972](https://github.com/WordPress/gutenberg/pull/47972)) +- Create automatic change higher order reducer. ([48312](https://github.com/WordPress/gutenberg/pull/48312)) +- [Inserter]: Preload media categories empty check - client side. ([47503](https://github.com/WordPress/gutenberg/pull/47503)) + +#### Post Editor +- Revert iframed editor for WP core only. ([48076](https://github.com/WordPress/gutenberg/pull/48076)) +- PageAttributesCheck: Return boolean value directly from the selector. ([48336](https://github.com/WordPress/gutenberg/pull/48336)) +- Apply busy status to the publish button in progress and unify button width. ([48444](https://github.com/WordPress/gutenberg/pull/48444)) + +#### Block Library +- Add all allowed innerblocks to the inspector animation experiment. ([47834](https://github.com/WordPress/gutenberg/pull/47834)) +- Make the Site Logo block placeholder state smaller. ([48218](https://github.com/WordPress/gutenberg/pull/48218)) +- ToggleControl: Remove margin overrides and add opt-in prop. ([47866](https://github.com/WordPress/gutenberg/pull/47866)) +- Post Terms block: Add transforms for variations. ([48328](https://github.com/WordPress/gutenberg/pull/48328)) +- List block: Add Firefox end-to-end tests. ([48271](https://github.com/WordPress/gutenberg/pull/48271)) +- Navigation List View: Add block movers to the more menu. ([48099](https://github.com/WordPress/gutenberg/pull/48099)) +- Navigation: Always create a fallback menu. ([47684](https://github.com/WordPress/gutenberg/pull/47684)) +- Navigation: Don't create a fallback navigation menu if there are inner blocks. ([48585](https://github.com/WordPress/gutenberg/pull/48585)) +- Navigation: Don't create duplicate navigation menus. ([48599](https://github.com/WordPress/gutenberg/pull/48599)) +- Navigation: Update the dependencies for the useEffect that handles notifications. ([48066](https://github.com/WordPress/gutenberg/pull/48066)) +- Navigation: Wrap the dependent functions in useCallback. ([48195](https://github.com/WordPress/gutenberg/pull/48195)) +- Page List: Move the modal to its own file. ([47922](https://github.com/WordPress/gutenberg/pull/47922)) +- [New Block] Add post time to read block. ([43403](https://github.com/WordPress/gutenberg/pull/43403)) +- Classic Block: Replace the deprecated `isPrimary` prop with `variant`. ([48230](https://github.com/WordPress/gutenberg/pull/48230)) +- Refactor core blocks to use HTML Tag Processor. ([43178](https://github.com/WordPress/gutenberg/pull/43178)) + +#### Block API +- HTML Tag Processor: Add WP 6.3 compat layer. ([47933](https://github.com/WordPress/gutenberg/pull/47933)) +- Tag Processor: Add bookmark invalidation logic. ([47559](https://github.com/WordPress/gutenberg/pull/47559)) + +#### Design Tools +- Duotone: Use the style engine to generate CSS for Duotone. ([48281](https://github.com/WordPress/gutenberg/pull/48281)) +- Duotone: Add Global Styles controls for blocks that support duotone. ([48255](https://github.com/WordPress/gutenberg/pull/48255)) +- Use Duotone presets in block duotone attributes. ([48318](https://github.com/WordPress/gutenberg/pull/48318)) +- Duotone: Use CSS variables instead of slugs in block attributes. ([48426](https://github.com/WordPress/gutenberg/pull/48426)) +- Border Panel: Add missing dep for `onBorderChange` callback. ([48010](https://github.com/WordPress/gutenberg/pull/48010)) +- Disable layout toolbar controls for content locked blocks. ([47939](https://github.com/WordPress/gutenberg/pull/47939)) +- SpacingSizesControl: Fix white dot on thumb. ([48574](https://github.com/WordPress/gutenberg/pull/48574)) + +#### List View +- Scroll selected block into view when single block selection changes. ([46895](https://github.com/WordPress/gutenberg/pull/46895)) + +#### Components +- TabPanel: Refactor unit tests in prep for controlled component updates. ([48086](https://github.com/WordPress/gutenberg/pull/48086)) +- Change higher order `with-constraint-tabbing` from `.js` to `.tsx`. ([48162](https://github.com/WordPress/gutenberg/pull/48162)) +- CircularOptionPicker: Refactor to TypeScript. ([47937](https://github.com/WordPress/gutenberg/pull/47937)) +- Extract delete handler to hook for RichText. ([48273](https://github.com/WordPress/gutenberg/pull/48273)) +- ToolsPanel: Remove unnecessary dep from resetAll callback. ([48011](https://github.com/WordPress/gutenberg/pull/48011)) +- Link Control - Add support for text only labels. ([47930](https://github.com/WordPress/gutenberg/pull/47930)) +- Pin `floating-ui/react-dom` version. ([48402](https://github.com/WordPress/gutenberg/pull/48402)) +- Use React 18 rendering for import dropdown. ([48244](https://github.com/WordPress/gutenberg/pull/48244)) + +### Bug Fixes + +#### Site Editor +- Fix Site Editor perf tests. ([48240](https://github.com/WordPress/gutenberg/pull/48240)) +- Fix routing for Classic themes using block-based template parts. ([48343](https://github.com/WordPress/gutenberg/pull/48343)) +- Fix the 'Browse all' link in the template details modal. ([48301](https://github.com/WordPress/gutenberg/pull/48301)) +- Fix the site editor home page loading when installed in a subdirectory. ([48363](https://github.com/WordPress/gutenberg/pull/48363)) +- Fix the tooltip and shortcut for the global save button. ([48282](https://github.com/WordPress/gutenberg/pull/48282)) +- Prevent distracting focused back button on site editor load. ([48472](https://github.com/WordPress/gutenberg/pull/48472)) +- Sort templates and template parts by slug. ([48473](https://github.com/WordPress/gutenberg/pull/48473)) +- Specify focus state color for template navigation button. ([48134](https://github.com/WordPress/gutenberg/pull/48134)) +- [Site Editor]: Set `html` block as freeform fallback block. ([48129](https://github.com/WordPress/gutenberg/pull/48129)) +- Remove broken site editor redirect. ([48283](https://github.com/WordPress/gutenberg/pull/48283)) +- Fix: Inserter does not appear on sidebar navigation. ([48623](https://github.com/WordPress/gutenberg/pull/48623)) +- Fix: Make navigation page list load its items on navigation sidebar. ([47853](https://github.com/WordPress/gutenberg/pull/47853)) +- Revert Site Tagline placeholder attribute, move example to block.json. ([48383](https://github.com/WordPress/gutenberg/pull/48383)) + +#### Block Editor +- MediaReplaceFlow: Fix styling for LinkControl. ([47949](https://github.com/WordPress/gutenberg/pull/47949)) +- Fix "Reset All" button in the typography panel of the block inspector. ([48123](https://github.com/WordPress/gutenberg/pull/48123)) +- [Inserter - Media tab]: Upload Openverse images when inserted. ([48501](https://github.com/WordPress/gutenberg/pull/48501)) +- Block alignment controls: Don't remount the block when alignments change. ([48209](https://github.com/WordPress/gutenberg/pull/48209)) +- Pin `valtio` version. ([48237](https://github.com/WordPress/gutenberg/pull/48237)) +- Fix block style preview flickering when a 'style' is focused. ([48418](https://github.com/WordPress/gutenberg/pull/48418)) +- Enable access to block settings within UBE. ([48435](https://github.com/WordPress/gutenberg/pull/48435)) +- Fix: Content only CPT template locking. ([48434](https://github.com/WordPress/gutenberg/pull/48434)) +- Firefox: Fix input rules (React async state issue). ([48210](https://github.com/WordPress/gutenberg/pull/48210)) +- Fix: Multiple overwrites on rest_controller_class for wp_template/wp_template_part. ([48078](https://github.com/WordPress/gutenberg/pull/48078)) +- Select blocks in `outline` list. ([48118](https://github.com/WordPress/gutenberg/pull/48118)) +- Only add layout classes to inner wrapper if block is a container. ([48611](https://github.com/WordPress/gutenberg/pull/48611)) + +#### Widgets Editor +- Widget Editor: Fix a problem with 'Move to Widget Area' button not working. ([48233](https://github.com/WordPress/gutenberg/pull/48233)) + +#### Block Library +- Add support for orientation-based block movers to core/social-links. ([48452](https://github.com/WordPress/gutenberg/pull/48452)) +- Fix Post Excerpt: Read more link is always on new line in the editor. ([47772](https://github.com/WordPress/gutenberg/pull/47772)) +- Fix: OffCanvasEditor does not inserts submenu on collapsed items. ([48214](https://github.com/WordPress/gutenberg/pull/48214)) +- List Item: Avoid an error when the 'onReplace' prop is undefined. ([48639](https://github.com/WordPress/gutenberg/pull/48639)) +- Add missing code on the fix page list loading PR. ([48621](https://github.com/WordPress/gutenberg/pull/48621)) +- Prevent text decoration from showing up in all blocks. ([48117](https://github.com/WordPress/gutenberg/pull/48117)) +- Widget Importer: Don't render controls when there's nothing to import. ([48396](https://github.com/WordPress/gutenberg/pull/48396)) +- [Query Loop] Fix top border in pattern selection modal. ([48303](https://github.com/WordPress/gutenberg/pull/48303)) +- [Query Loop] Sync `gutenberg_build_query_vars_from_query_block` with core. ([48640](https://github.com/WordPress/gutenberg/pull/48640)) +- Avatar: Clean up border application in editor. ([48024](https://github.com/WordPress/gutenberg/pull/48024)) + +#### Global Styles +- Fix for `WP_Theme_JSON_Resolver_Gutenberg::Get_merged_data`. ([48644](https://github.com/WordPress/gutenberg/pull/48644)) +- Display preset names via the tooltip. ([48304](https://github.com/WordPress/gutenberg/pull/48304)) +- Fix typo in valid settings for fluid typography. ([48605](https://github.com/WordPress/gutenberg/pull/48605)) +- Prevent the global styles previews thumbnails from resizing on load. ([48474](https://github.com/WordPress/gutenberg/pull/48474)) +- Site Tagline: Add example so that it will display in style book. ([48300](https://github.com/WordPress/gutenberg/pull/48300)) +- Fix wrong property name letterSpacing to lineHeight. ([48091](https://github.com/WordPress/gutenberg/pull/48091)) +- Update code position for constants and methods in `WP_Theme_JSON_Gutenberg` class. ([48631](https://github.com/WordPress/gutenberg/pull/48631)) +- `WP_Theme_JSON_Gutenberg`: Update comments to be aligned with core, so backports are easier. ([48624](https://github.com/WordPress/gutenberg/pull/48624)) + +### Accessibility +- Autocomplete: Duplicate list within iframe for non visual users. ([47907](https://github.com/WordPress/gutenberg/pull/47907)) +- Fix site editor switch mode message. ([48136](https://github.com/WordPress/gutenberg/pull/48136)) +- Fix the Publish region position and focus style. ([48102](https://github.com/WordPress/gutenberg/pull/48102)) +- Focus 1st parent block on block remove, if no previous block is available. ([48204](https://github.com/WordPress/gutenberg/pull/48204)) +- List view: Allow selected block to override roving tabindex. ([48339](https://github.com/WordPress/gutenberg/pull/48339)) +- Style Book: Allow button text labels for style book icon. ([48088](https://github.com/WordPress/gutenberg/pull/48088)) +- Style Book: Focus the Style Book when opened, and enable ESCAPE key to close. ([48151](https://github.com/WordPress/gutenberg/pull/48151)) +- Try to fix a11y test flakiness. ([48236](https://github.com/WordPress/gutenberg/pull/48236)) +- Make the template customized info accessible. ([48159](https://github.com/WordPress/gutenberg/pull/48159)) + + +### Performance + +#### Block Editor +- Block Editor: Improve empty `getBlockParents()` perf. ([48242](https://github.com/WordPress/gutenberg/pull/48242)) +- Fix perf regression in duotone hooks. ([48401](https://github.com/WordPress/gutenberg/pull/48401)) +- Writing flow: Avoid recalc style on every selection change. ([48409](https://github.com/WordPress/gutenberg/pull/48409)) +- useAsyncList: Flush state updates when processing queue. ([48238](https://github.com/WordPress/gutenberg/pull/48238)) + +#### Components +- Autocomplete: Reduce work before finding trigger. ([48327](https://github.com/WordPress/gutenberg/pull/48327)) +- Disable lazy term meta loading render_block_core_template_part. ([48000](https://github.com/WordPress/gutenberg/pull/48000)) +- Rich text: Only selectively handle keyup/pointerup. ([48385](https://github.com/WordPress/gutenberg/pull/48385)) +- Rich text: Remove unnecessary handleSelectionChange call. ([48373](https://github.com/WordPress/gutenberg/pull/48373)) +- Rich text: Try removing store change on focus. ([48342](https://github.com/WordPress/gutenberg/pull/48342)) +- Autocomplete: Avoid calling setState on input. ([48565](https://github.com/WordPress/gutenberg/pull/48565)) +- Autocomplete: Don't change state on every keystroke. ([48485](https://github.com/WordPress/gutenberg/pull/48485)) + +### Code Quality +- Tag Processor: Make `$html` field `protected`. ([48681](https://github.com/WordPress/gutenberg/pull/48681)) +- WP_HTML_Tag_Processor: Support tag closer bookmarks. ([48692](https://github.com/WordPress/gutenberg/pull/48692)) +- Fixed incorrect type annotations in @wordpress/data, part 2. ([48221](https://github.com/WordPress/gutenberg/pull/48221)) +- Lodash: Refactor away from `_.merge()`. ([48239](https://github.com/WordPress/gutenberg/pull/48239)) +- Lodash: Remove from integration tests. ([48122](https://github.com/WordPress/gutenberg/pull/48122)) +- Lodash: Remove remaining `_.get()` from `core-data`. ([48310](https://github.com/WordPress/gutenberg/pull/48310)) +- Lodash: Remove `_.get()` for post type usages. ([48121](https://github.com/WordPress/gutenberg/pull/48121)) +- Lodash: Remove remaining `_.get()` from `editor`. ([48314](https://github.com/WordPress/gutenberg/pull/48314)) +- Lodash: Remove some `_.get()` from editor. ([48104](https://github.com/WordPress/gutenberg/pull/48104)) +- Lodash: Refactor context system provider away from `_.merge()`. ([48036](https://github.com/WordPress/gutenberg/pull/48036)) +- Lodash: Remove `_.merge()` from `getMappedColumnWidths()`. ([48032](https://github.com/WordPress/gutenberg/pull/48032)) +- Lodash: Remove some `_.get()` from Gallery block. ([48488](https://github.com/WordPress/gutenberg/pull/48488)) +- Lodash: Remove some `_.get()` from Image block. ([48489](https://github.com/WordPress/gutenberg/pull/48489)) +- Playwright: Fix request utils for non Docker envs. ([48206](https://github.com/WordPress/gutenberg/pull/48206)) +- [Private APIs] Only prevent module re-registration if IS_WORDPRESS_CORE. ([48352](https://github.com/WordPress/gutenberg/pull/48352)) +- Add a manual performance job that we can trigger from Github UI. ([48302](https://github.com/WordPress/gutenberg/pull/48302)) +- Track new front-end metric: LCP-TTFB. ([48288](https://github.com/WordPress/gutenberg/pull/48288)) + +### Documentation +- Clarify backport status of `appearance-tools` theme support. ([48622](https://github.com/WordPress/gutenberg/pull/48622)) +- Docs: Clarify release process for major WP Beta 1. ([48248](https://github.com/WordPress/gutenberg/pull/48248)) +- Docs: Markdown fix for wp_enqueue_script usage. ([48212](https://github.com/WordPress/gutenberg/pull/48212)) +- Fix `e2e-test-utils-playwright`'s jsdoc and types. ([48266](https://github.com/WordPress/gutenberg/pull/48266)) +- Fix incorrect css property in editor-color-palette documentation. ([48333](https://github.com/WordPress/gutenberg/pull/48333)) +- Proactively update versions in WordPress ahead of 6.2. ([48145](https://github.com/WordPress/gutenberg/pull/48145)) +- Update curation document to include theme.json filters. ([48576](https://github.com/WordPress/gutenberg/pull/48576)) +- Updates to the curating the editor experience to include 6.1 & 6.2 items. ([48294](https://github.com/WordPress/gutenberg/pull/48294)) + +### Tools +#### Testing +- Migrate `switch-to-draft` to Playwright. ([48120](https://github.com/WordPress/gutenberg/pull/48120)) +- VizReg end-to-end tests: Programmatically test all combinations of a given list of props/values. ([48260](https://github.com/WordPress/gutenberg/pull/48260)) +- Update end-to-end test snapshots to Jest 29 default. ([48626](https://github.com/WordPress/gutenberg/pull/48626)) +- Update snapshot format to Jest 29 default. ([48366](https://github.com/WordPress/gutenberg/pull/48366)) +- Move react-native-editor parser tests to a test directory. ([48615](https://github.com/WordPress/gutenberg/pull/48615)) +- `ToolsPanel`: Refactor unit tests to TypeScript. ([48275](https://github.com/WordPress/gutenberg/pull/48275)) +- Migrate block deletion end-to-end tests to Playwright. ([48012](https://github.com/WordPress/gutenberg/pull/48012)) +- Migrate list view end-to-end tests to Playwright. ([47919](https://github.com/WordPress/gutenberg/pull/47919)) +- Navigation block: End-to-end code quality fixes. ([48071](https://github.com/WordPress/gutenberg/pull/48071)) +- Remove old end-to-end tests for the navigation block. ([48126](https://github.com/WordPress/gutenberg/pull/48126)) +- Update assertion Autocomplete end-to-end tests. ([48344](https://github.com/WordPress/gutenberg/pull/48344)) +- [Automated Testing]: Fix wrong button fixture. ([48305](https://github.com/WordPress/gutenberg/pull/48305)) +- Add typescript-eslint rules with type informations to end-to-end tests. ([48267](https://github.com/WordPress/gutenberg/pull/48267)) +- Fix snapshots for Spacer mobile unit tests. ([48406](https://github.com/WordPress/gutenberg/pull/48406)) +- Add command to run performance tests in debug mode. ([48614](https://github.com/WordPress/gutenberg/pull/48614)) +- Make the performance tests more stable. ([48094](https://github.com/WordPress/gutenberg/pull/48094)) + +## First time contributors + +The following PRs were merged by first time contributors: + +- @abhi3315: Link Control - Add support for text only labels. ([47930](https://github.com/WordPress/gutenberg/pull/47930)) +- @hbhalodia: Update: Change higher order `with-constraint-tabbing` from `.js` to `.tsx`. ([48162](https://github.com/WordPress/gutenberg/pull/48162)) +- @sboerrigter: Fix incorrect css property in editor-color-palette documentation. ([48333](https://github.com/WordPress/gutenberg/pull/48333)) +- @shreyasikhar: Site Editor Sidebar: Add line-height for template/parts name and update width for edit button. ([48160](https://github.com/WordPress/gutenberg/pull/48160)) +- @suvrodattamitu: Style Engine: Fix wrong property name letterSpacing to lineHeight. ([48091](https://github.com/WordPress/gutenberg/pull/48091)) +- @tomdevisser: Remove copy for managing a block's style variations. ([48367](https://github.com/WordPress/gutenberg/pull/48367)) + + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @abhi3315 @adamziel @afercia @alexstine @andrewserong @annezazu @brookewp @carolinan @chad1008 @ciampo @dcalhoun @draganescu @ellatrix @fluiddot @geriux @getdave @gvgvgvijayan @gziolo @hbhalodia @jorgefilipecosta @jsnajdr @kevin940726 @MaggieCabrera @Mamaduka @marekdedic @ndiego @ntsekouras @oandregal @ockham @richtabor @sboerrigter @scruffian @shreyasikhar @spacedmonkey @suvrodattamitu @t-hamano @tellthemachines @tomdevisser @tyxla @WunderBart @youknowriad + + = 15.2.4 = @@ -4706,7 +6507,7 @@ The following contributors merged PRs in this release: - Docs: Fix required status of `onSelectUrl` prop of `MediaReplaceFlow` component. ([44025](https://github.com/WordPress/gutenberg/pull/44025)) - Document new global styles filters. ([44111](https://github.com/WordPress/gutenberg/pull/44111)) - Document template_lock=noContent for Custom Post Types. ([43977](https://github.com/WordPress/gutenberg/pull/43977)) -- InnerBlocks: document that `templateLock:NoContent` cannot be overriden by children. ([43825](https://github.com/WordPress/gutenberg/pull/43825)) +- InnerBlocks: document that `templateLock:NoContent` cannot be overridden by children. ([43825](https://github.com/WordPress/gutenberg/pull/43825)) - Theme.json: Add default values for settings.spacing.spacingScale. ([43860](https://github.com/WordPress/gutenberg/pull/43860)) - Theme.json: Fix schema for useRootPaddingAwareAlignments. ([43628](https://github.com/WordPress/gutenberg/pull/43628)) - Typography block supports: Call tear_down in tests and format PHP doc blocks. ([43968](https://github.com/WordPress/gutenberg/pull/43968)) diff --git a/docs/README.md b/docs/README.md index 4d5343a76141e9..41c694faf71f89 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,41 +1,74 @@ # Block Editor Handbook -**Gutenberg** is a codename for a whole new paradigm in WordPress site building and publishing, that aims to revolutionize the entire publishing experience as much as Gutenberg did the printed word. The project is right now in the second phase of a four-phase process that will touch every piece of WordPress -- Editing, **Customization** (which includes Full Site Editing, Block Patterns, Block Directory and Block based themes), Collaboration, and Multilingual -- and is focused on a new editing experience, the block editor (which is the topic of the current documentation). +Hi! 👋 Welcome to the Block Editor Handbook. -![Quick view of the block editor](https://raw.githubusercontent.com/WordPress/gutenberg/trunk/docs/assets/quick-view-of-the-block-editor.png) +The [**Block editor**](https://wordpress.org/gutenberg/) is a modern and up-to-date paradigm for WordPress site building and publishing. It uses a modular system of **Blocks** to compose and format content, and is designed to create rich and flexible layouts for websites and digital products. -**Legend :** +The editor consists of several primary elements, as shown in the following figure: -1. Block Inserter -2. Block editor content area -3. Settings Sidebar +![Quick view of the block editor](https://raw.githubusercontent.com/WordPress/gutenberg/trunk/docs/assets/overview-block-editor-2023.png) -Using a system of Blocks to compose and format content, the new block-based editor is designed to create rich, flexible layouts for websites and digital products. Content is created in the unit of blocks instead of freeform text with inserted media, embeds and Shortcodes (there's a Shortcode block though). +The elements highlighted in the figure are: -Blocks treat Paragraphs, Headings, Media, and Embeds all as components that, when strung together, make up the content stored in the WordPress database, replacing the traditional concept of freeform text with embedded media and shortcodes. The new editor is designed with progressive enhancement, meaning that it is back-compatible with all legacy content, and it also offers a process to try to convert and split a Classic block into equivalent blocks using client-side parsing. Finally, the blocks offer enhanced editing and format controls. +1. **Inserter**: A panel for inserting blocks into the content canvas +2. **Content canvas**: The content editor, which holds content created with blocks +3. **Settings sidebar**: A sidebar panel for configuring a block’s settings (among other things) -The Editor offers rich new value to users with visual, drag-and-drop creation tools and powerful developer enhancements with modern vendor packages, reusable components, rich APIs and hooks to modify and extend the editor through Custom Blocks, Custom Block Styles and Plugins. +Through the Block editor, you create content modularly using Blocks. There are a number of [core blocks](https://developer.wordpress.org/block-editor/reference-guides/core-blocks/) ready to be used, and you can also [create your own custom block](https://developer.wordpress.org/block-editor/getting-started/create-block/). -[Learn to use the block editor](https://wordpress.org/support/article/wordpress-editor/) to create media-rich posts and pages. +A [Block](https://developer.wordpress.org/block-editor/explanations/architecture/key-concepts/#blocks) is a discrete element such as a Paragraph, Heading, Media element, or Embed. Each block is treated as a separate element with individual editing and format controls. When all these components are pieced together, they make up the content that is then [stored in the WordPress database](https://developer.wordpress.org/block-editor/explanations/architecture/data-flow/#serialization-and-parsing). -## Quick links +The Block Editor is the result of the [work done on the **Gutenberg project**](https://developer.wordpress.org/block-editor/explanations/faq/#what-is-gutenberg) which is aimed to revolutionize the WordPress editing experience. -### Create a Block Tutorial +Besides offering an [enhanced editing experience](https://wordpress.org/gutenberg/) through visual content creation tools, the Block Editor is also a powerful developer platform with a [rich feature set of APIs](https://developer.wordpress.org/block-editor/reference-guides/) that allow it to be manipulated and extended in a multitude of different ways. -[Learn how to create your first block](/docs/getting-started/create-block/README.md) for the WordPress block editor. From setting up your development environment, tools, and getting comfortable with the new development model, this tutorial covers all what you need to know to get started with the block editor. +## Navigating this handbook -### Develop for the block editor +This handbook is focused on block development and is divided into five sections, each serving a different purpose. -Whether you want to extend the functionality of the block editor, or create a plugin based on it, [see the developer documentation](/docs/how-to-guides/README.md) to find all the information about the basic concepts you need to get started, the block editor APIs and its architecture. -- [Gutenberg Architecture](/docs/explanations/architecture/README.md) -- [Block Styles](/docs/reference-guides/block-api/block-styles.md) -- [Creating Block Patterns](/docs/reference-guides/block-api/block-patterns.md) -- [Theming for the Block Editor](/docs/how-to-guides/themes/README.md) -- [Block API Reference](/docs/reference-guides/block-api/README.md) -- [Block Editor Accessibility](/docs/how-to-guides/accessibility.md) -- [Internationalization](/docs/how-to-guides/internationalization.md) +**[Getting Started](https://developer.wordpress.org/block-editor/getting-started/)** -### Contribute to the block editor +For those just starting out with block development this is where you can get set up with a [development environment](https://developer.wordpress.org/block-editor/getting-started/devenv/) and learn the [fundamentals of block development](https://developer.wordpress.org/block-editor/getting-started/create-block/). -Everything you need to know to [start contributing to the block editor](/docs/contributors/README.md) . Whether you are interested in the design, code, triage, documentation, support or internationalization of the block editor, you will find here guides to help you. + +**[How-to Guides](https://developer.wordpress.org/block-editor/how-to-guides/)** + +Here you can build on what you learned in the Getting Started section and learn how to solve particular problems that you might encounter. You can also get tutorials, and example code that you can reuse, for projects such as [building a full-featured block](https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/) or [working with WordPress’ data](https://developer.wordpress.org/block-editor/how-to-guides/data-basics/). In addition you can learn [How to use JavaScript with the Block Editor](https://developer.wordpress.org/block-editor/how-to-guides/javascript/). + + +**[Reference Guides](https://developer.wordpress.org/block-editor/reference-guides/)** + +This section is the heart of the handbook and is where you can get down to the nitty-gritty and look up the details of the particular API that you’re working with or need information on. Among other things, the [Block API Reference](https://developer.wordpress.org/block-editor/reference-guides/block-api/) covers most of what you will want to do with a block, and each [component](https://developer.wordpress.org/block-editor/reference-guides/components/) and [package](https://developer.wordpress.org/block-editor/reference-guides/packages/) is also documented here. _Components are also documented via [Storybook](https://wordpress.github.io/gutenberg/?path=/story/docs-introduction--page)._ + + +**[Explanations](https://developer.wordpress.org/block-editor/explanations/)** + +This section enables you to go deeper and reinforce your practical knowledge with a theoretical understanding of the [Architecture](https://developer.wordpress.org/block-editor/explanations/architecture/) of the block editor. Its [Glossary of terms](https://developer.wordpress.org/block-editor/explanations/glossary/) and [FAQs](https://developer.wordpress.org/block-editor/explanations/faq/) should answer any outstanding questions you may have. + + +**[Contributor Guide](https://developer.wordpress.org/block-editor/contributors/)** + +Gutenberg is open source software and anyone is welcome to contribute to the project. This section details how to contribute and can help you choose in which way you want to contribute, whether that be with [code](https://developer.wordpress.org/block-editor/contributors/code/), with [design](https://developer.wordpress.org/block-editor/contributors/design/), with [documentation](https://developer.wordpress.org/block-editor/contributors/documentation/), or in some other way. + + +## Further resources + +This handbook should be considered the canonical resource for all things related to block development. However there are other resources that can help you. + +- [**WordPress Developer Blog**](https://developer.wordpress.org/news/) - An ever-growing resource of technical articles covering specific topics related to block development and a wide variety of use cases. The blog is also an excellent way to [keep up with the latest developments in WordPress](https://developer.wordpress.org/news/tag/roundup/). +- [**Learn WordPress**](https://learn.wordpress.org/) - The WordPress hub for learning resources where you can find courses like [Introduction to Block Development: Build your first custom block](https://learn.wordpress.org/course/introduction-to-block-development-build-your-first-custom-block/), [Converting a Shortcode to a Block](https://learn.wordpress.org/course/converting-a-shortcode-to-a-block/) or [Using the WordPress Data Layer](https://learn.wordpress.org/course/using-the-wordpress-data-layer/) +- [**WordPress.tv**](https://wordpress.tv/) - A hub of WordPress-related videos (from talks at WordCamps to recordings of online workshops) curated and moderated by the WordPress.org community. You’re sure to find something to aid your learning about [block development](https://wordpress.tv/?s=block%20development&sort=newest) or the [block-editor](https://wordpress.tv/?s=block%20editor&sort=relevance) here. +- [**Gutenberg repository**](https://github.com/WordPress/gutenberg/) - Development of the block editor project is carried out in this GitHub repository. It contains the code of interesting packages such as [`block-library`](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src) (core blocks) or [`components`](https://github.com/WordPress/gutenberg/tree/trunk/packages/components) (common UI elements). _The [gutenberg-examples](https://github.com/WordPress/gutenberg-examples) repository is another useful reference._ + + +## Are you in the right place? + +[This handbook](https://developer.wordpress.org/block-editor) is targeted at those seeking to develop for the block editor, but several other handbooks exist for WordPress developers under [developer.wordpress.org](http://developer.wordpress.org/): + +- [/themes](https://developer.wordpress.org/themes) - Theme Handbook +- [/plugins](https://developer.wordpress.org/plugins) - Plugin Handbook +- [/apis](https://developer.wordpress.org/apis) - Common APIs Handbook +- [/advanced-administration](https://developer.wordpress.org/advanced-administration) - WP Advanced Administration Handbook +- [/rest-api](https://developer.wordpress.org/rest-api/) - REST API Handbook +- [/coding-standards](https://developer.wordpress.org/coding-standards) - Best practices for WordPress developers \ No newline at end of file diff --git a/docs/assets/overview-block-editor-2023.png b/docs/assets/overview-block-editor-2023.png new file mode 100644 index 00000000000000..ad3a6e1ff71923 Binary files /dev/null and b/docs/assets/overview-block-editor-2023.png differ diff --git a/docs/assets/plugin-sidebar-text-control-custom-field.png b/docs/assets/plugin-sidebar-text-control-custom-field.png new file mode 100644 index 00000000000000..0a0d6d2e088eb2 Binary files /dev/null and b/docs/assets/plugin-sidebar-text-control-custom-field.png differ diff --git a/docs/contributors/code/coding-guidelines.md b/docs/contributors/code/coding-guidelines.md index d9d62450c94bf9..1954020c1a3eb1 100644 --- a/docs/contributors/code/coding-guidelines.md +++ b/docs/contributors/code/coding-guidelines.md @@ -114,133 +114,47 @@ Example: import VisualEditor from '../visual-editor'; ``` -### Experimental and Unstable APIs +### Legacy Experimental APIs, Plugin-only APIs, and Private APIs -Experimental and unstable APIs are temporary values exported from a module whose existence is either pending future revision or provides an immediate means to an end. +#### Legacy Experimental APIs -_To External Consumers:_ - -**There is no support commitment for experimental and unstable APIs.** They can and will be removed or changed without advance warning, including as part of a minor or patch release. As an external consumer, you should avoid these APIs. - -_To Project Contributors:_ - -An experimental or unstable API is named as such to communicate instability of a function whose interface is not yet finalized. Aside from references within the code, these APIs should neither be documented nor mentioned in any CHANGELOG. They should effectively be considered to not exist from an external perspective. In most cases, they should only be exposed to satisfy requirements between packages maintained in this repository. - -An experimental or unstable function or object should be prefixed respectively using `__experimental` or `__unstable`. - -```js -export { __experimentalDoExcitingExperimentalAction } from './api'; -export { __unstableDoTerribleAwfulAction } from './api'; -``` - -- An **experimental API** is one which is planned for eventual public availability, but is subject to further experimentation, testing, and discussion. -- An **unstable API** is one which serves as a means to an end. It is not desired to ever be converted into a public API. - -In both cases, the API should be made stable or removed at the earliest opportunity. - -While an experimental API may often stabilize into a publicly-available API, there is no guarantee that it will. The conversion to a stable API will inherently be considered a breaking change by the mere fact that the function name must be changed to remove the `__experimental` prefix. +Historically, Gutenberg has used the `__experimental` and `__unstable` prefixes to indicate that a given API is not yet stable and may be subject to change. This is a legacy convention which should be avoided in favor of the plugin-only API pattern or a private API pattern described below. -#### Experimental APIs merged into WordPress Core become a liability +The problem with using the prefixes was that these APIs rarely got stabilized or removed. As of June 2022, WordPress Core contained 280 publicly exported experimental APIs merged from the Gutenberg plugin during the major WordPress releases. Many plugins and themes started relying on these experimental APIs for essential features that couldn't be accessed in any other way. -**Avoid introducing public experimental APIs.** +The legacy `__experimental` APIs can't be removed on a whim anymore. They became a part of the WordPress public API and fall under the [WordPress Backwards Compatibility policy](https://developer.wordpress.org/block-editor/contributors/code/backward-compatibility/). Removing them involves a deprecation process. It may be relatively easy for some APIs, but it may require effort and span multiple WordPress releases for others. -As of June 2022, WordPress Core contains 280 publicly exported experimental APIs. They got merged from the Gutenberg -plugin during the major WordPress releases. Many plugins and themes rely on these experimental APIs for essential -features that can't be accessed in any other way. Naturally, these APIs can't be removed without a warning anymore. -They are a part of the WordPress public API and fall under the -[WordPress Backwards Compatibility policy](https://developer.wordpress.org/block-editor/contributors/code/backward-compatibility/). -Removing them involves a deprecation process. It may be relatively easy for some APIs, but it may require effort and -span multiple WordPress releases for others. +All in all, don't use the `__experimental` prefix for new APIs. Use plugin-only APIs and private APIs instead. -**Use private experimental APIs instead.** +#### Plugin-only APIs -Make your experimental APIs private and don't expose them to WordPress extenders. +Plugin-only APIs are temporary values exported from a module whose existence is either pending future revision or provides an immediate means to an end. -This way they'll remain internal implementation details that can be changed or removed -without a warning and without breaking WordPress plugins. - -The tactical guidelines below will help you write code without introducing new experimental APIs. - -#### General guidelines - -Some `__experimental` functions are exported in _package A_ and only used in a single _package B_ and nowhere else. Consider removing such functions from _package A_ and making them private and non-exported members of _package B_. +_To External Consumers:_ -If your experimental API is only meant for the Gutenberg Plugin but not for the next WordPress major release, consider limiting the export to the plugin environment. For example, `@wordpress/components` could do that to receive early feedback about a new Component, but avoid bringing that component to WordPress core: +**There is no support commitment for plugin-only APIs.** They can and will be removed or changed without advance warning, including as part of a minor or patch release. As an external consumer, you should avoid these APIs. -```js -if ( IS_GUTENBERG_PLUGIN ) { - export { __experimentalFunction } from './private-apis'; -} -``` +_To Project Contributors:_ -#### Replace experimental selectors with hooks +An **plugin-only API** is one which is planned for eventual public availability, but is subject to further experimentation, testing, and discussion. It should be made stable or removed at the earliest opportunity. -Sometimes a non-exported React hook suffices as a substitute for introducing a new experimental selectors: +Plugin-only APIs are excluded from WordPress Core and only available in the Gutenberg Plugin: ```js -// Instead of this: -// selectors.js: -export function __unstableHasActiveBlockOverlayActive( state, parent ) { - /* ... */ -} -export function __unstableIsWithinBlockOverlay( state, clientId ) { - let parent = state.blocks.parents[ clientId ]; - while ( !! parent ) { - if ( __unstableHasActiveBlockOverlayActive( state, parent ) ) { - return true; - } - parent = state.blocks.parents[ parent ]; - } - return false; -} -// MyComponent.js: -function MyComponent( { clientId } ) { - const { __unstableIsWithinBlockOverlay } = useSelect( myStore ); - const isWithinBlockOverlay = __unstableIsWithinBlockOverlay( clientId ); - // ... -} - -// Consider this: -// MyComponent.js: -function hasActiveBlockOverlayActive( selectors, parent ) { - /* ... */ -} -function useIsWithinBlockOverlay( clientId ) { - return useSelect( ( select ) => { - const selectors = select( blockEditorStore ); - let parent = selectors.getBlockRootClientId( clientId ); - while ( !! parent ) { - if ( hasActiveBlockOverlayActive( selectors, parent ) ) { - return true; - } - parent = selectors.getBlockRootClientId( parent ); - } - return false; - } ); -} -function MyComponent( { clientId } ) { - const isWithinBlockOverlay = useIsWithinBlockOverlay( clientId ); - // ... +// Using process.env.IS_GUTENBERG_PLUGIN allows Webpack to exclude this +// export from WordPress core: +if ( process.env.IS_GUTENBERG_PLUGIN ) { + export { doSomethingExciting } from './api'; } ``` -#### Dispatch experimental actions in thunks - -Turning an existing public action into a [thunk](/docs/how-to-guides/thunks.md) -enables dispatching private actions inline: +The public interface of such APIs is not yet finalized. Aside from references within the code, they APIs should neither be documented nor mentioned in any CHANGELOG. They should effectively be considered to not exist from an external perspective. In most cases, they should only be exposed to satisfy requirements between packages maintained in this repository. -```js -export function toggleFeature( scope, featureName ) { - return function ( { dispatch } ) { - dispatch( { type: '__experimental_BEFORE_TOGGLE' } ); - // ... - }; -} -``` +While a plugin-only API may often stabilize into a publicly-available API, there is no guarantee that it will. -#### Use the `lock()` and `unlock()` API from `@wordpress/private-apis` to privately export almost anything +#### Private APIs -Each `@wordpress` package wanting to privately access or expose experimental APIs can +Each `@wordpress` package wanting to privately access or expose a private APIs can do so by opting-in to `@wordpress/private-apis`: ```js @@ -264,10 +178,10 @@ Once the package opted-in, you can use the `lock()` and `unlock()` utilities: export const publicObject = {}; // However, this string is internal and should not be publicly available: -const __experimentalString = '__experimental information'; +const privateString = 'private information'; // Solution: lock the string "inside" of the object: -lock( publicObject, __experimentalString ); +lock( publicObject, privateString ); // The string is not nested in the object and cannot be extracted from it: console.log( publicObject ); @@ -275,65 +189,65 @@ console.log( publicObject ); // The only way to access the string is by "unlocking" the object: console.log( unlock( publicObject ) ); -// "__experimental information" +// "private information" // lock() accepts all data types, not just strings: export const anotherObject = {}; -lock( anotherObject, function __experimentalFn() {} ); +lock( anotherObject, function privateFn() {} ); console.log( unlock( anotherObject ) ); -// function __experimentalFn() {} +// function privateFn() {} ``` Keep reading to learn how to use `lock()` and `unlock()` to avoid publicly exporting -different kinds of `__experimental` APIs. +different kinds of `private` APIs. -##### Experimental selectors and actions +##### Private selectors and actions You can attach private selectors and actions to a public store: ```js // In packages/package1/store.js: -import { __experimentalHasContentRoleAttribute, ...selectors } from './selectors'; -import { __experimentalToggleFeature, ...actions } from './selectors'; -// The `lock` function is exported from the internal experiments.js file where +import { privateHasContentRoleAttribute, ...selectors } from './selectors'; +import { privateToggleFeature, ...actions } from './selectors'; +// The `lock` function is exported from the internal private-apis.js file where // the opt-in function was called. import { lock, unlock } from './private-apis'; export const store = registerStore(/* ... */); // Attach a private action to the exported store: unlock( store ).registerPrivateActions({ - __experimentalToggleFeature + privateToggleFeature } ); // Attach a private action to the exported store: unlock( store ).registerPrivateSelectors({ - __experimentalHasContentRoleAttribute + privateHasContentRoleAttribute } ); // In packages/package2/MyComponent.js: import { store } from '@wordpress/package1'; import { useSelect } from '@wordpress/data'; -// The `unlock` function is exported from the internal experiments.js file where +// The `unlock` function is exported from the internal private-apis.js file where // the opt-in function was called. import { unlock } from './private-apis'; function MyComponent() { const hasRole = useSelect( ( select ) => ( // Use the private selector: - unlock( select( store ) ).__experimentalHasContentRoleAttribute() + unlock( select( store ) ).privateHasContentRoleAttribute() // Note the unlock() is required. This line wouldn't work: - // select( store ).__experimentalHasContentRoleAttribute() + // select( store ).privateHasContentRoleAttribute() ) ); // Use the private action: - unlock( useDispatch( store ) ).__experimentalToggleFeature(); + unlock( useDispatch( store ) ).privateToggleFeature(); // ... } ``` -##### Experimental functions, classes, and variables +##### Private functions, classes, and variables ```js // In packages/package1/index.js: @@ -342,12 +256,12 @@ import { lock } from './private-apis'; export const privateApis = {}; /* Attach private data to the exported object */ lock( privateApis, { - __experimentalCallback: function () {}, - __experimentalReactComponent: function ExperimentalComponent() { + privateCallback: function () {}, + privateReactComponent: function PrivateComponent() { return
; }, - __experimentalClass: class Experiment {}, - __experimentalVariable: 5, + privateClass: class PrivateClass {}, + privateVariable: 5, } ); // In packages/package2/index.js: @@ -355,11 +269,11 @@ import { privateApis } from '@wordpress/package1'; import { unlock } from './private-apis'; const { - __experimentalCallback, - __experimentalReactComponent, - __experimentalClass, - __experimentalVariable, -} = unlock( experiments ); + privateCallback, + privateReactComponent, + privateClass, + privateVariable, +} = unlock( privateApis ); ``` Remember to always register the private actions and selectors on the **registered** store. @@ -391,10 +305,10 @@ unlock( registeredStore ).registerPrivateActions( { } ); ``` -#### Experimental function arguments +#### Private function arguments -To add an experimental argument to a stable function you'll need -to prepare a stable and an experimental version of that function. +To add a private argument to a stable function you'll need +to prepare a stable and a private version of that function. Then, export the stable function and `lock()` the unstable function inside it: @@ -402,11 +316,11 @@ inside it: // In @wordpress/package1/index.js: import { lock } from './private-apis'; -// The experimental function contains all the logic -function __experimentalValidateBlocks( formula, __experimentalIsStrict ) { +// A private function contains all the logic +function privateValidateBlocks( formula, privateIsStrict ) { let isValid = false; // ...complex logic we don't want to duplicate... - if ( __experimentalIsStrict ) { + if ( privateIsStrict ) { // ... } // ...complex logic we don't want to duplicate... @@ -415,25 +329,27 @@ function __experimentalValidateBlocks( formula, __experimentalIsStrict ) { } // The stable public function is a thin wrapper that calls the -// experimental function with the experimental features disabled +// private function with the private features disabled export function validateBlocks( blocks ) { - __experimentalValidateBlocks( blocks, false ); + privateValidateBlocks( blocks, false ); } -lock( validateBlocks, __experimentalValidateBlocks ); + +export const privateApis = {}; +lock( privateApis, { privateValidateBlocks } ); // In @wordpress/package2/index.js: -import { validateBlocks } from '@wordpress/package1'; +import { privateApis as package1PrivateApis } from '@wordpress/package1'; import { unlock } from './private-apis'; -// The experimental function may be "unlocked" given the stable function: -const __experimentalValidateBlocks = unlock( validateBlocks ); -__experimentalValidateBlocks( blocks, true ); +// The private function may be "unlocked" given the stable function: +const { privateValidateBlocks } = unlock( package1PrivateApis ); +privateValidateBlocks( blocks, true ); ``` -#### Experimental React Component properties +#### Private React Component properties -To add an experimental argument to a stable component you'll need -to prepare a stable and an experimental version of that component. +To add an private argument to a stable component you'll need +to prepare a stable and an private version of that component. Then, export the stable function and `lock()` the unstable function inside it: @@ -441,41 +357,42 @@ inside it: // In @wordpress/package1/index.js: import { lock } from './private-apis'; -// The experimental component contains all the logic -const ExperimentalMyButton = ( { title, __experimentalShowIcon = true } ) => { +// The private component contains all the logic +const PrivateMyButton = ( { title, privateShowIcon = true } ) => { // ...complex logic we don't want to duplicate... return ( ); } // The stable public component is a thin wrapper that calls the -// experimental component with the experimental features disabled +// private component with the private features disabled export const MyButton = ( { title } ) => - + -lock(MyButton, ExperimentalMyButton); +export const privateApis = {}; +lock( privateApis, { PrivateMyButton } ); // In @wordpress/package2/index.js: -import { MyButton } from '@wordpress/package1'; +import { privateApis } from '@wordpress/package1'; import { unlock } from './private-apis'; -// The experimental component may be "unlocked" given the stable component: -const ExperimentalMyButton = unlock(MyButton); +// The private component may be "unlocked" given the stable component: +const { PrivateMyButton } = unlock(privateApis); export function MyComponent() { return ( - + ) } ``` -#### Experimental editor settings +#### Private editor settings -WordPress extenders cannot update the experimental block settings on their own. The `updateSettings()` actions of the `@wordpress/block-editor` store will filter out all the settings that are **not** a part of the public API. The only way to actually store them is via private action. `__experimentalUpdateSettings()`. +WordPress extenders cannot update the private block settings on their own. The `updateSettings()` actions of the `@wordpress/block-editor` store will filter out all the settings that are **not** a part of the public API. The only way to actually store them is via the private action `__experimentalUpdateSettings()`. To privatize a block editor setting, add it to the `privateSettings` list in [/packages/block-editor/src/store/actions.js](/packages/block-editor/src/store/actions.js): @@ -486,13 +403,51 @@ const privateSettings = [ ]; ``` -#### Experimental block.json and theme.json APIs +#### Private block.json and theme.json APIs As of today, there is no way to restrict the `block.json` and `theme.json` APIs -to the Gutenberg codebase. In the future, however, the new `__experimental` APIs +to the Gutenberg codebase. In the future, however, the new private APIs will only apply to the core WordPress blocks and plugins and themes will not be able to access them. +#### Inline small actions in thunks + +Finally, instead of introducing a new action creator, consider using a [thunk](/docs/how-to-guides/thunks.md): + +```js +export function toggleFeature( scope, featureName ) { + return function ( { dispatch } ) { + dispatch( { type: '__private_BEFORE_TOGGLE' } ); + // ... + }; +} +``` + +### Exposing private APIs publicly + +Some private APIs could benefit from community feedback and it makes sense to expose them to WordPress extenders. At the same time, it doesn't make sense to turn them into a public API in WordPress core. What should you do? + +You can re-export that private API as a plugin-only API to expose it publicly only in the Gutenberg plugin: + +```js +// This function can't be used by extenders in any context: +function privateEverywhere() {} + +// This function can be used by extenders with the Gutenberg plugin but not in vanilla WordPress Core: +function privateInCorePublicInPlugin() {} + +// Gutenberg treats both functions as private APIs internally: +const privateApis = {}; +lock(privateApis, { privateEverywhere, privateInCorePublicInPlugin }); + +// The privateInCorePublicInPlugin function is explicitly exported, +// but this export will not be merged into WordPress core thanks to +// the process.env.IS_GUTENBERG_PLUGIN check. +if ( process.env.IS_GUTENBERG_PLUGIN ) { + export const privateInCorePublicInPlugin = unlock( privateApis ).privateInCorePublicInPlugin; +} +``` + ### Objects When possible, use [shorthand notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#New_notations_in_ECMAScript_2015) when defining object property values: diff --git a/docs/contributors/code/e2e/README.md b/docs/contributors/code/e2e/README.md index 8ad4abdfe4d384..e5c4cbb534102c 100644 --- a/docs/contributors/code/e2e/README.md +++ b/docs/contributors/code/e2e/README.md @@ -2,8 +2,11 @@ This living document serves to prescribe instructions and best practices for writing end-to-end (E2E) tests with Playwright in the Gutenberg project. -> **Note** -> See the dedicated guide if you're working with the previous Jest + Puppeteer framework. See the [migration guide](https://github.com/WordPress/gutenberg/tree/HEAD/docs/contributors/code/e2e/migration.md) if you're migrating tests from Jest + Puppeteer. +
+ +See the dedicated guide if you're working with the previous Jest + Puppeteer framework. See the [migration guide](https://github.com/WordPress/gutenberg/tree/HEAD/docs/contributors/code/e2e/migration.md) if you're migrating tests from Jest + Puppeteer. +
+ ## Running tests @@ -38,14 +41,11 @@ xvfb-run -- npm run test:e2e:playwright -- --project=webkit Read the [best practices](https://playwright.dev/docs/best-practices) guide for Playwright. -
-

Forbid `$`, use `locator` instead

+### Forbid `$`, use `locator` instead In fact, any API that returns `ElementHandle` is [discouraged](https://playwright.dev/docs/api/class-page#page-query-selector). This includes `$`, `$$`, `$eval`, `$$eval`, etc. [`Locator`](https://playwright.dev/docs/api/class-locator) is a much better API and can be used with playwright's [assertions](https://playwright.dev/docs/api/class-locatorassertions). This also works great with Page Object Model since that locator is lazy and doesn't return a promise. -
-
-

Use accessible selectors

+### Use accessible selectors Use [`getByRole`](https://playwright.dev/docs/locators#locate-by-role) to construct the query wherever possible. It enables us to write accessible queries without having to rely on internal implementations. @@ -63,45 +63,32 @@ page.getByRole( 'region', { name: 'Block Library' } ) ``` See the [official documentation](https://playwright.dev/docs/locators) for more info on how to use them. -
-
-

Selectors are strict by default

+### Selectors are strict by default To encourage better practices for querying elements, selectors are [strict](https://playwright.dev/docs/api/class-browser#browser-new-page-option-strict-selectors) by default, meaning that it will throw an error if the query returns more than one element. -
-
-

Don't overload test-utils, inline simple utils

+### Don't overload test-utils, inline simple utils `e2e-test-utils` are too bloated with too many utils. Most of them are simple enough to be inlined directly in tests. With the help of accessible selectors, simple utils are easier to write now. For utils that only take place on a certain page, use Page Object Model instead (with an exception of clearing states with `requestUtils` which are better placed in `e2e-test-utils`). Otherwise, only create an util if the action is complex and repetitive enough. -
-
-

Favor Page Object Model over utils

+### Favor Page Object Model over utils As mentioned above, [Page Object Model](https://playwright.dev/docs/test-pom) is the preferred way to create reusable utility functions on a certain page. The rationale behind using a POM is to group utils under namespaces to be easier to discover and use. In fact, `PageUtils` in the `e2e-test-utils-playwright` package is also a POM, which avoids the need for global variables, and utils can reference each other with `this`. -
-
-

Restify actions to clear or set states

+### Restify actions to clear or set states It's slow to set states manually before or after tests, especially when they're repeated multiple times between tests. It's recommended to set them via API calls. Use `requestUtils.rest` and `requestUtils.batchRest` instead to call the [REST API](https://developer.wordpress.org/rest-api/reference/) (and add them to `requestUtils` if needed). We should still add a test for manually setting them, but that should only be tested once. -
-
-

Avoid global variables

+### Avoid global variables Previously in our Jest + Puppeteer E2E tests, `page` and `browser` are exposed as global variables. This makes it harder to work with when we have multiple pages/tabs in the same test, or if we want to run multiple tests in parallel. `@playwright/test` has the concept of [fixtures](https://playwright.dev/docs/test-fixtures) which allows us to inject `page`, `browser`, and other parameters into the tests. -
-
-

Make explicit assertions

+### Make explicit assertions -We can insert as many assertions in one test as needed. It's better to make explicit assertions whenever possible. For instance, if we want to assert that a button exists before clicking on it, we can do `expect( locator ).toBeVisible()` before performing `locator.click()`. This makes the tests flow better and easier to read. -
+We can insert as many assertions in one test as needed. It's better to make explicit assertions whenever possible. For instance, if we want to assert that a button exists before clicking on it, we can do `expect( locator ).toBeVisible()` before performing `locator.click()`. This makes the tests flow better and easier to read ## Common pitfalls diff --git a/docs/contributors/code/e2e/migration.md b/docs/contributors/code/e2e/migration.md index 030c6c87f2c304..4b0400b85d3c06 100644 --- a/docs/contributors/code/e2e/migration.md +++ b/docs/contributors/code/e2e/migration.md @@ -17,7 +17,7 @@ This document outlines a typical flow of migrating a Jest + Puppeteer test to Pl ## Migration steps for test utils -Before migrating a test utility function, think twice about whether it's necessary. Playwright offers a lot of readable and powerful APIs which make a lot of the utils obsolete. Try implementing the same thing inline directly in the test first. Only follow the below guide if that doesn't work for you. Some examples of utils that deserve to be implemented in the `e2e-test-utils-playwright` package include complex browser APIs (like `pageUtils.dragFiles` and `pageUtils.pressKeyWithModifier`) and APIs that set states (`requestUtils.*`). +Before migrating a test utility function, think twice about whether it's necessary. Playwright offers a lot of readable and powerful APIs which make a lot of the utils obsolete. Try implementing the same thing inline directly in the test first. Only follow the below guide if that doesn't work for you. Some examples of utils that deserve to be implemented in the `e2e-test-utils-playwright` package include complex browser APIs (like `pageUtils.dragFiles` and `pageUtils.pressKeys`) and APIs that set states (`requestUtils.*`). > **Note** > The `e2e-test-utils-playwright` package is not meant to be a drop-in replacement of the Jest + Puppeteer's `e2e-test-utils` package. Some utils are only created to ease the migration process, but they are not necessarily required. @@ -25,7 +25,7 @@ Before migrating a test utility function, think twice about whether it's necessa Playwright utilities are organized a little differently from those in the `e2e-test-utils` package. The `e2e-test-utils-playwright` package has the following folders that utils are divided up into: - `admin` - Utilities related to WordPress admin or WordPress admin's user interface (e.g. `visitAdminPage`). - `editor` - Utilities for the block editor (e.g. `clickBlockToolbarButton`). -- `pageUtils` - General utilities for interacting with the browser (e.g. `pressKeyWithModifier`). +- `pageUtils` - General utilities for interacting with the browser (e.g. `pressKeys`). - `requestUtils` - Utilities for making REST API requests (e.g. `activatePlugin`). These utilities are used for setup and teardown of tests. 1. Copy the existing file in `e2e-test-utils` and paste it in the `admin`, `editor`, `page` or `request` folder in `e2e-test-utils-playwright` depending on the type of util. diff --git a/docs/contributors/code/getting-started-with-code-contribution.md b/docs/contributors/code/getting-started-with-code-contribution.md index b4bc1275cf1ea0..ab691fbcd68afa 100644 --- a/docs/contributors/code/getting-started-with-code-contribution.md +++ b/docs/contributors/code/getting-started-with-code-contribution.md @@ -126,7 +126,7 @@ Port: {MYSQL_PORT_NUMBER} **Please note**: the MySQL port number will change each time `wp-env` restarts. If you find you can no longer access your database, simply repeat the steps above to find the new port number and restore your connection. -**Tip**: [Sequel Ace](https://sequel-ace.com/) is a useful GUI tool for accessing a MySQL database. Other tools are available and documented in this [article on accessing the WordPress database](https://wordpress.org/support/article/creating-database-for-wordpress/). +**Tip**: [Sequel Ace](https://sequel-ace.com/) is a useful GUI tool for accessing a MySQL database. Other tools are available and documented in this [article on accessing the WordPress database](https://wordpress.org/documentation/article/creating-database-for-wordpress/). #### Troubleshooting 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 72cad307673861..e5c8fdd4eaf2c1 100644 --- a/docs/contributors/code/react-native/getting-started-react-native.md +++ b/docs/contributors/code/react-native/getting-started-react-native.md @@ -7,7 +7,7 @@ Welcome! This is the Getting Started guide for the native mobile port of the blo For a developer experience closer to the one the project maintainers current have, make sure you have the following tools installed: - git -- [nvm](https://github.com/creationix/nvm) +- [nvm](https://github.com/nvm-sh/nvm) - Node.js and npm (use nvm to install them) - [Android Studio](https://developer.android.com/studio/) to be able to compile the Android version of the app - [Xcode](https://developer.apple.com/xcode/) to be able to compile the iOS app @@ -111,7 +111,7 @@ This project is set up to use [jest](https://facebook.github.io/jest/) for tests This repository uses Appium to run UI tests. The tests live in `__device-tests__` and are written using Appium to run tests against simulators and real devices. To run these you'll need to check off a few things: - When running the tests, you'll need to ensure the Metro bundler (`npm run native start`) is not running. -- [Appium CLI](https://github.com/appium/appium/blob/HEAD/docs/en/about-appium/getting-started.md) installed and available globally. We also recommend using [appium-doctor](https://github.com/appium/appium-doctor) to ensure all of Appium's dependencies are good to go. You don't have to worry about starting the server yourself, the tests handle starting the server on port 4723, just be sure that the port is free or feel free to change the port number in the test file. +- [Appium CLI](https://appium.io/docs/en/about-appium/getting-started/) installed and available globally. We also recommend using [appium-doctor](https://github.com/appium/appium-doctor) to ensure all of Appium's dependencies are good to go. You don't have to worry about starting the server yourself, the tests handle starting the server on port 4723, just be sure that the port is free or feel free to change the port number in the test file. - For iOS a simulator should automatically launch but for Android you'll need to have an emulator _with at least platform version 8.0_ fired up and running. Then, to run the UI tests on iOS: @@ -132,7 +132,7 @@ To run a single test instead of the entire suite, use `npm run native device-tes npm run native test:e2e:android:local gutenberg-editor-paragraph.test.js ``` -Note: You might experience problems that seem to be related to the tests starting the Appium server, e.g. errors that say `Connection Refused`, `Connection Reset` or `The requested environment is not available`. For now, you can manually start the Appium server via [appium desktop](https://github.com/appium/appium-desktop) or the CLI, then change the port number in the tests while (optionally) commenting out related code in the `beforeAll` and `afterAll` block. +Note: You might experience problems that seem to be related to the tests starting the Appium server, e.g. errors that say `Connection Refused`, `Connection Reset` or `The requested environment is not available`. For now, you can manually start the Appium server via [Appium Inspector](https://github.com/appium/appium-inspector/) or the CLI, then change the port number in the tests while (optionally) commenting out related code in the `beforeAll` and `afterAll` block. For a more detailed outline of the UI tests and how to get started writing one, please visit the [UI Test documentation](/packages/react-native-editor/__device-tests__/README.md) and our [contributing guide](/packages/react-native-editor/__device-tests__/CONTRIBUTING.md). diff --git a/docs/contributors/code/react-native/osx-setup-guide.md b/docs/contributors/code/react-native/osx-setup-guide.md index 98b6721f4cd023..5d6d4603e49598 100644 --- a/docs/contributors/code/react-native/osx-setup-guide.md +++ b/docs/contributors/code/react-native/osx-setup-guide.md @@ -74,7 +74,7 @@ Install [Xcode](https://developer.apple.com/xcode/) via the app store and then o - Accept the license agreement. - Verify that `Xcode > Preferences > Locations > Command Line Tools` points to the current Xcode version. -Screenshot of XCode command line tools settings. +Screenshot of Xcode command line tools settings. ### react-native doctor diff --git a/docs/contributors/code/release.md b/docs/contributors/code/release.md index 991f256806bf96..3dd8863859e37f 100644 --- a/docs/contributors/code/release.md +++ b/docs/contributors/code/release.md @@ -2,7 +2,10 @@ This Repository is used to perform several types of releases. This document serves as a checklist for each one of these. It is helpful if you'd like to understand the different workflows. -To release a stable version of the Gutenberg plugin you need to be part of the [Gutenberg development team](/docs/block-editor/contributors/repository-management/#teams). On top of that, you need approval from a member of the Gutenberg Core team for the final step of the release process (upload to the WordPress.org plugin repo -- see below). If you aren't a member yourself, make sure to contact one ahead of time so they'll be around at the time of the release. You can ping in the [#core-editor Slack channel](https://wordpress.slack.com/messages/C02QB2JS7). +To release a stable version of the Gutenberg plugin you need: +- To be part of the [Gutenberg development team](/docs/block-editor/contributors/repository-management/#teams), to launch the GitHub actions related to the release process and to potentially backport PRs to the release branch. +- Write permissions in [make.wordpress.org/core](make.wordpress.org/core), to draft the release post. +- On top of that, for the last step of the process (uploading the new version to the WordPress.org.plugin directory), you will need approval from a member of the Gutenberg Core team -- see below for more details). To [release WordPress's npm packages](#packages-releases-to-npm-and-wordpress-core-updates), similar requirements apply. @@ -14,7 +17,7 @@ We release a new major version approximately every two weeks. The current and ne - **On the date of the current milestone**, we publish a release candidate and make it available for plugin authors and users to test. If any regressions are found with a release candidate, a new one can be published. On this date, all remaining PRs on the milestone are moved automatically to the next release. Release candidates should be versioned incrementally, starting with `-rc.1`, then `-rc.2`, and so on. [Preparation of the release post starts here](/docs/block-editor/contributors/code/release/#writing-the-release-notes-and-post) and spans until the final release. -- **One week after the first release candidate**, the stable version is created based on the last release candidate and any necessary regression fixes. Once the stable version is released, the release post is published, including a [performance audit](/docs/block-editor/contributors/testing-overview/#performance-testing). +- **One week after the first release candidate**, the stable version is created based on the last release candidate and any necessary regression fixes. Once the stable version is released and the release post is published. If critical bugs are discovered on stable versions of the plugin, patch versions can be released at any time. @@ -42,11 +45,11 @@ This message is misleading and no action is required by the release coordinator. #### View the release draft -As soon as the workflow has finished, you'll find the release draft under [https://github.com/WordPress/gutenberg/releases](https://github.com/WordPress/gutenberg/releases). The draft is pre-populated with changelog entries based on previous release candidates for this version, and any changes that have since been cherry-picked to the release branch. Thus, when releasing the first stable version of a series, make sure to delete any RC version headers (that are only there for your information) and to move the more recent changes to the correct section (see below). +As soon as the workflow has finished, you'll find the release draft under [Gutenberg Releases](https://github.com/WordPress/gutenberg/releases). The draft is pre-populated with changelog entries based on previous release candidates for this version and any changes that have since been cherry-picked to the release branch. Thus, when releasing the first stable version of a series, delete any RC version headers (that are only there for your information) and move the more recent changes to the correct section (see below). -The changelog draft will be at least partially pre-organized (based on GitHub label) into sections and within those into "features". Take some time to read the generated notes and then edit them to ensure legibility and accuracy. +The changelog draft will be partially pre-organized (based on GitHub labels) into sections and, within those, into “features.” Take some time to read the generated notes and then edit them to ensure legibility and accuracy. -Don't rush this part -- it's important to bring the release notes into a nice shape. You don't have to do it all in one go -- you can save the draft and come back to it later. +Don't rush this part. It's important to ensure the release notes are as organized as possible, and it doesn't need to be completed in one go. You can save the draft and come back to it later. When editing the notes, you should be sure to: @@ -55,36 +58,39 @@ When editing the notes, you should be sure to: You can find some more tips on writing the release notes and post in the section below. -#### Creating Release Candidate Patches (done via `git cherry-pick`) +#### Creating Release Candidate Patches At any point after the release candidate has been published but before the final stable release, some bugs related to this release might be fixed and committed to `trunk`. These fixes won't automatically be incorporated into the final stable release, including them is a manual process. There are a couple of ways a release coordinator might be made aware of these bugs: -- Contributors may add the `Backport to Gutenberg RC` label to a closed PR. [https://github.com/WordPress/gutenberg/pulls?q=is%3Apr+label%3A%22Backport+to+Gutenberg+RC%22+is%3Aclosed](Do a search for any of these PRs) before publishing the final release. + +- Contributors may add the `Backport to Gutenberg RC` label to a closed PR. [Do a search for any of these PRs](https://github.com/WordPress/gutenberg/pulls?q=is%3Apr+label%3A%22Backport+to+Gutenberg+RC%22+is%3Aclosed) before publishing the final release. - You may receive a direct message or a ping in the #core-editor channel in slack notifying you of PRs that need to be included in the release candidate. Even when this is the case, the `Backport to Gutenberg RC` should be added to the PR. The cherry-picking process is handled as follows: + 1. Checkout the corresponding release branch with: `git checkout release/x.x`. 2. Cherry-pick fix commits (in chronological order) with `git cherry-pick [SHA]`. The cherry-picking process can be automated with the [`npm run cherry-pick` script](/docs/contributors/code/auto-cherry-picking.md), but make sure to use the `Backport to Gutenberg RC` label when running the command. 3. When done, push the changes to GitHub: `git push`. -Tip: To find the `[SHA]` for a pull request, open the PR and near the end you'll see a message "[Username] merged commit [SHA] into `trunk`". +
+To find the `[SHA]` for a pull request, open the PR and near the end you'll see a message "[Username] merged commit [SHA] into `trunk`". +
Once the commits have been cherry-picked, remove the `Backport to Gutenberg RC` label and update the milestone to the current release for all cherry-picked PRs. -If you decide that the fixes deserve another release candidate before the stable version is published, create one by following the instructions above. Let other contributors know that a new release candidate has been released in the [`#core-editor` channel](https://wordpress.slack.com/messages/C02QB2JS7). +If you decide that the fixes deserve another release candidate before the stable version is published, create one by following the instructions above. Let other contributors know that a new release candidate has been released in the [`#core-editor`](https://wordpress.slack.com/messages/C02QB2JS7) Slack channel. #### Publishing the release -Only once you're happy with the shape of the release notes should you press the green "Publish release" button. This will create a `git` tag for the version, publish the release, and trigger [another GHA workflow](https://github.com/WordPress/gutenberg/actions/workflows/upload-release-to-plugin-repo.yml) that has a twofold purpose: +Only once you're happy with the shape of the release notes, press the green "Publish release" button. This will create a `git` tag for the version, publish the release, and trigger [another GHA workflow](https://github.com/WordPress/gutenberg/actions/workflows/upload-release-to-plugin-repo.yml) with a twofold purpose: 1. Use the release notes that you just edited to update `changelog.txt`, and 2. Upload the new plugin version to the WordPress.org plugin repository (SVN) (only if you're releasing a stable version). -The latter step needs approval by a member of the Gutenberg Core team. Locate the ["Upload Gutenberg plugin to WordPress.org plugin repo" workflow](https://github.com/WordPress/gutenberg/actions/workflows/upload-release-to-plugin-repo.yml) for the new version, and have it [approved](https://docs.github.com/en/actions/managing-workflow-runs/reviewing-deployments#approving-or-rejecting-a-job). +The last step needs approval by a member of either the Gutenberg Core team, WordPress core team, or Gutenberg Release team. These teams get a notification email when the release is ready to be approved, but if time is of the essence you can ask in the [#core-editor Slack channel] or ping the [Gutenberg Release team]([url](https://github.com/orgs/WordPress/teams/gutenberg-release)) to speed up the process; reaching out _before_ launching the release process so that somebody is ready to approve is recommended. Locate the ["Upload Gutenberg plugin to WordPress.org plugin repo" workflow](https://github.com/WordPress/gutenberg/actions/workflows/upload-release-to-plugin-repo.yml) for the new version, and have it [approved](https://docs.github.com/en/actions/managing-workflow-runs/reviewing-deployments#approving-or-rejecting-a-job). -This will cause the new version to be available to users of WordPress all over the globe! 💃 -You should check that folks can install the new version from their Dashboard. +Once approved, the new Gutenberg version will be available to WordPress users all over the globe. You should check that folks can install the latest version from their WordPress Dashboard. Once released, all that's left to do is writing a release post on [make.wordpress.org/core](https://make.wordpress.org/core/). You can find some tips on that below. @@ -97,8 +103,7 @@ Documenting the release is a group effort between the release manager, Gutenberg 1. Curating the changelog - Wednesday after the RC release to Friday 2. Selecting the release highlights - Friday to Monday 3. Drafting the release post - Monday to Wednesday -4. Running the performance tests - Wednesday right after the stable release -5. Publishing the post - Wednesday after stable release +4. Publishing the post - Wednesday after stable release #### 1. Curating the changelog @@ -134,40 +139,27 @@ When possible, the highlighted changes in the release post should include an ani These visual assets should maintain consistency with previous release posts; using lean, white themes helps in this regard and visually integrate well with the [make.wordpress.org/core](https://make.wordpress.org/core/) blog aesthetics. Including copyrighted material should be avoided, and browser plugins that can be seen in the browser canvas (spell checkers, form fillers, etc.) disabled when capturing the assets. -#### 4. Running the performance tests - -The post should also include a performance audit at the end, comparing the current Gutenberg release with both the previous one and the latest WordPress major version. There are GitHub worfklows in place to do this comparison as part of the Continuous Integration setup, so the performance audit results can be found at the workflow run generated by the release commit in the [Performance Tests workflows](https://github.com/WordPress/gutenberg/actions/workflows/performance.yml) page, with the job name `Compare performance with current WordPress Core and previous Gutenberg versions`. - -If the GitHub workflow fails, the performance audit can be executed locally using `bin/plugin/cli.js perf` and passing the branches to run the performance suite against as parameters. In addition, the current major WP version can be passed to avoid running tests against the WP `trunk`. Example: - -``` -node ./bin/plugin/cli.js perf release/x.y release/x.z wp/a.b --wp-version wp.major -``` - -The performance values usually displayed in the release post are: - -- Time to the first block (test named `firstBlock`) -- KeyPress Event (typing) (test named `type`) - #### 5. Publishing the post Once the post content is ready, an author already having permissions to post in [make.wordpress.org/core](https://make.wordpress.org/core/) will create a new draft and import the content; this post should be published after the actual release, helping external media to echo and amplify the release news. Remember asking for peer review is encouraged by the [make/core posting guidelines](https://make.wordpress.org/core/handbook/best-practices/post-comment-guidelines/#peer-review)! ### Creating Minor Releases -Occasionally it's necessary to create a minor release (i.e. X.Y._Z_) of the Plugin. This is usually done to expedite fixes for bad regressions or bugs. The `Backport to Gutenberg Minor Release` is usually used to identify PRs that need to be included in a minor release, but as release coordinator you may also be notified more informally through slack. Even so, it's good to ensure all relevant PRs have the correct label. +Occasionally it's necessary to create a minor release (i.e. X.Y.**Z**) of the Plugin. This is usually done to expedite fixes for bad regressions or bugs. The `Backport to Gutenberg Minor Release` is usually used to identify PRs that need to be included in a minor release, but as release coordinator you may also be notified more informally through slack. Even so, it's good to ensure all relevant PRs have the correct label. As you proceed with the following process, it's worth bearing in mind that such minor releases are not created as branches in their own right (e.g. `release/12.5.0`) but are simply [tags](https://github.com/WordPress/gutenberg/releases/tag/v12.5.1). -The method for minor releases is nearly identical to the main Plugin release process (see above) but has some notable exceptions. Please make sure to read _the whole_ of this guide before proceeding. +The method for minor releases is nearly identical to the main Plugin release process (see above) but has some notable exceptions. Please make sure to read _the entire_ guide before proceeding. #### Updating the release branch The minor release should only contain the _specific commits_ required. To do this you should checkout the previous _major_ stable (i.e. non-RC) release branch (e.g. `release/12.5`) locally and then cherry pick any commits that you require into that branch. -_IMPORTANT:_ If an RC already exists for a new version, you _need_ to cherry-pick the same commits in the respective release branch, as they will not be included automatically. E.g.: If you're about to release a new minor release for 12.5 and just cherry-picked into `release/12.5`, but 12.6.0-rc.1 is already out, then you need to cherry-pick the same commits into the `release/12.6` branch, or they won't be included in subsequent releases for 12.6! Usually it's best to coordinate this process with the release coordinator for the next release. +
+If an RC already exists for a new version, you _need_ to cherry-pick the same commits in the respective release branch, as they will not be included automatically. E.g.: If you're about to release a new minor release for 12.5 and just cherry-picked into `release/12.5`, but 12.6.0-rc.1 is already out, then you need to cherry-pick the same commits into the `release/12.6` branch, or they won't be included in subsequent releases for 12.6! Usually it's best to coordinate this process with the release coordinator for the next release. +
-The cherry-picking process can be automated with the [`npm run cherry-pick` script](/docs/contributors/code/auto-cherry-picking.md), but be sure to use the `Backport to Gutenberg Minor Release` label when running the script. +The cherry-picking process can be automated with the [`npm run cherry-pick`](/docs/contributors/code/auto-cherry-picking.md) script, but be sure to use the `Backport to Gutenberg Minor Release` label when running the script. You must also ensure that all PRs being included are assigned to the Github Milestone on which the minor release is based. Bear in mind, that when PRs are _merged_ they are automatically assigned a milestone for the next _stable_ release. Therefore you will need to go back through each PR in Github and re-assign the Milestone. @@ -209,7 +201,7 @@ This is expected. The draft release will contain only the plugin zip. Only once > Do I need to publish point releases to WordPress.org? -Yes. The method for this is identical to the main Plugin release process. You will need a Gutenberg Core team member to approve the release workflow. +Yes. The method for this is identical to the main Plugin release process. You will need a member of the Gutenberg Core team the Gutenberg Release team to approve the release workflow. > The release process failed to cherry-pick version bump commit to the trunk branch. @@ -345,7 +337,7 @@ Behind the scenes, the rest of the process is automated with `./bin/plugin/cli.j 5. Run the script `npm run publish:latest`. - When asked for the version numbers to choose for each package pick the values of the updated CHANGELOG files. - You'll be asked for your One-Time Password (OTP) a couple of times. This is the code from the 2FA authenticator app you use. Depending on how many packages are to be released you may be asked for more than one OTP, as they tend to expire before all packages are released. - - If the publishing process ends up incomplete (perhaps because it timed-out or an bad OTP was introduce) you can resume it via [`lerna publish from-package`](https://github.com/lerna/lerna/tree/HEAD/commands/publish#bump-from-package). + - If the publishing process ends up incomplete (perhaps because it timed-out or an bad OTP was introduce) you can resume it via [`lerna publish from-package`](https://github.com/lerna/lerna/tree/main/libs/commands/publish#bump-from-package). 6. Finally, now that the npm packages are published, cherry-pick the commits created by lerna ("Publish" and the CHANGELOG update) into the `trunk` branch of Gutenberg. ### Development Releases diff --git a/docs/contributors/code/testing-overview.md b/docs/contributors/code/testing-overview.md index b646756669563d..42704be233b9b9 100644 --- a/docs/contributors/code/testing-overview.md +++ b/docs/contributors/code/testing-overview.md @@ -290,7 +290,30 @@ test('fires onChange when a new value is typed', async () => { }) ``` +### Integration testing for block UI +Integration testing is defined as a type of testing where different parts are tested as a group. In this case, the parts that we want to test are the different components that are required to be rendered for a specific block or editor logic. In the end, they are very similar to unit tests as they are run with the same command using the Jest library. The main difference is that for the integration tests the blocks are run within a [`special instance of the block editor`](https://github.com/WordPress/gutenberg/blob/trunk/test/integration/helpers/integration-test-editor.js#L60). + +The advantage of this approach is that the bulk of a block editor's functionality (block toolbar and inspector panel interactions, etc.) can be tested without having to fire up the full e2e test framework. This means the tests can run much faster and more reliably. It is suggested that as much of a block's UI functionality as possible is covered with integration tests, with e2e tests used for interactions that require a full browser environment, eg. file uploads, drag and drop, etc. + +[`The Cover block`](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/cover/test/edit.js) is an example of a block that uses this level of testing to provide coverage for a large percentage of the editor interactions. + +To set up a jest file for integration tests: + +```js +import { + initializeEditor +} from 'test/integration/helpers/integration-test-editor'; + +async function setup( attributes ) { + const testBlock = { name: 'core/cover', attributes }; + return initializeEditor( testBlock ); +} +``` + +The `initializeEditor` function returns the output of the `@testing-library/react` `render` method. It will also accept an array of block metadata objects, allowing you to set up the editor with multiple blocks. + +The integration test editor module also exports a `selectBlock` which can be used to select the block to be tested by the aria-label on the block wrapper, eg. "Block: Cover". ### Snapshot testing diff --git a/docs/contributors/documentation/README.md b/docs/contributors/documentation/README.md index f06abf9d0de7c4..a862592ab318ae 100644 --- a/docs/contributors/documentation/README.md +++ b/docs/contributors/documentation/README.md @@ -14,7 +14,7 @@ The Gutenberg project uses GitHub for managing code and tracking issues. The mai There are two major sets of documentation for the Gutenberg project: -1. [User documentation](https://wordpress.org/support/article/wordpress-editor/) is information on how to use the Editor as an author publishing posts. For contributing to user docs, follow the docs blog, or ask in the #docs Slack channel, to understand the current priorities. +1. [User documentation](https://wordpress.org/documentation/article/wordpress-block-editor/) is information on how to use the Editor as an author publishing posts. For contributing to user docs, follow the docs blog, or ask in the #docs Slack channel, to understand the current priorities. 2. [Block editor handbook](https://developer.wordpress.org/block-editor/) is everything related to the Gutenberg project including: developing, extending, and—what you are reading right now—contributing specific to Gutenberg. The rest of this document covers contributing to the block editor handbook. @@ -50,7 +50,7 @@ To update an existing page: 2. Create a branch to work, for example `docs/update-contrib-guide`. 3. Make the necessary changes to the existing document. 4. Commit your changes. -5. Create a pull request using "\[Type\] Documentation" label. +5. Create a pull request using the [\[Type\] Developer Documentation](https://github.com/WordPress/gutenberg/labels/%5BType%5D%20Developer%20Documentation) label. ### Create a new document @@ -103,6 +103,10 @@ Use the full directory and filename from the Gutenberg repository, not the publi An example, the link to this page is: `/docs/contributors/documentation/README.md` +
+Note: The usual link transformation is not applied to links in callouts. See below. +
+ ### Code examples The code example in markdown should be wrapped in three tick marks \`\`\` and should additionally include a language specifier. See this [GitHub documentation around fenced code blocks](https://help.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks). @@ -132,7 +136,7 @@ The preferred format for code examples is JSX, this should be the default view. The Block Editor handbook supports the same [notice styles as other WordPress handbooks](https://make.wordpress.org/docs/handbook/documentation-team-handbook/handbooks-style-and-formatting-guide/#formatting). However, the shortcode implementation is not ideal with the different locations the block editor handbook documentation is published (npm, GitHub). -The recommended way to implement in markdown is to use the raw HTML and `callout callout-LEVEL` classes. For example: +The recommended way to implement in markdown is to use the raw HTML and`callout callout-LEVEL` classes. For example: ```html
This is an **info** callout.
@@ -156,6 +160,14 @@ This is an **alert** callout. This is a **warning** callout.
+
+Note: In callout notices, links also need to be HTML `<a href></a>` notations. +The usual link transformation is not applied to links in callouts. +For instance, to reach the Getting started > Create Block page the URL in GitHub is +https://developer.wordpress.org/docs/getting-started/create-block/README.md +and will have to be hardcoded for the endpoint in the Block Editor Handbook as +https://developer.wordpress.org/block-editor/getting-started/create-block/ to link correctly in the handbook. +
### Editor config You should configure your editor to use Prettier to auto-format markdown documents. See the [Getting Started documentation](/docs/contributors/code/getting-started-with-code-contribution.md) for complete details. diff --git a/docs/contributors/localizing.md b/docs/contributors/localizing.md index 1745569f68f324..8b434e32d1ce8d 100644 --- a/docs/contributors/localizing.md +++ b/docs/contributors/localizing.md @@ -1,6 +1,6 @@ -# Localizing Gutenberg Plugin +# Localizing Gutenberg -The Gutenberg plugin is translated via the general plugin translation system (GlotPress) at https://translate.wordpress.org. Review the [GlotPress translation process documentation] (https://make.wordpress.org/polyglots/handbook/tools/glotpress-translate-wordpress-org/) for additional information. +The Gutenberg plugin is translated via the general plugin translation system (GlotPress) at https://translate.wordpress.org. Review the [GlotPress translation process documentation](https://make.wordpress.org/polyglots/handbook/tools/glotpress-translate-wordpress-org/) for additional information. To translate Gutenberg in your locale or language, [select your locale here](https://translate.wordpress.org/projects/wp-plugins/gutenberg) and translate *Development* (which contains the plugin's string) and/or *Development Readme* (please translate what you see in the Details tab of the [plugin page](https://wordpress.org/plugins/gutenberg/)). diff --git a/docs/contributors/release-screenshot.png b/docs/contributors/release-screenshot.png deleted file mode 100644 index 63334f489c0b2c..00000000000000 Binary files a/docs/contributors/release-screenshot.png and /dev/null differ diff --git a/docs/explanations/architecture/README.md b/docs/explanations/architecture/README.md index 723bce3ce4e2f4..774df183618d4a 100644 --- a/docs/explanations/architecture/README.md +++ b/docs/explanations/architecture/README.md @@ -2,12 +2,16 @@ Let’s look at the big picture and the architectural and UX principles of the block editor and the Gutenberg repository. -- [Key Concepts](/docs/explanations/architecture/key-concepts.md) -- [Data Format And Data Flow](/docs/explanations/architecture/data-flow.md) -- [Understand the repository folder structure](/docs/contributors/folder-structure.md). +## Editor + +- [Key concepts](/docs/explanations/architecture/key-concepts.md). +- [Data format and data flow](/docs/explanations/architecture/data-flow.md). +- [Site editing templates](/docs/explanations/architecture/full-site-editing-templates.md). +- [Styles in the editor](/docs/explanations/architecture/styles.md). +- [Performance](/docs/explanations/architecture/performance.md). + +## Gutenberg Repository + - [Modularity and WordPress Packages](/docs/explanations/architecture/modularity.md). -- [Block Editor Performance](/docs/explanations/architecture/performance.md). -- What are the decision decisions behind the Data Module? -- [Why is Puppeteer the tool of choice for end-to-end tests?](/docs/explanations/architecture/automated-testing.md) -- [What's the difference between the different editor packages? What's the purpose of each package?](/docs/explanations/architecture/modularity.md#whats-the-difference-between-the-different-editor-packages-whats-the-purpose-of-each-package) -- [Template and template parts flows](/docs/explanations/architecture/full-site-editing-templates.md) +- [Understand the repository folder structure](/docs/contributors/folder-structure.md). +- **Outdated!** [Why is Puppeteer the tool of choice for end-to-end tests?](/docs/explanations/architecture/automated-testing.md). diff --git a/docs/explanations/architecture/full-site-editing-templates.md b/docs/explanations/architecture/full-site-editing-templates.md index 76bc3a0f5861f2..aa18ad17a18f6f 100644 --- a/docs/explanations/architecture/full-site-editing-templates.md +++ b/docs/explanations/architecture/full-site-editing-templates.md @@ -1,4 +1,4 @@ -# Full Site Editing Templates +# Site Editing Templates ## Template and template part flows diff --git a/docs/explanations/architecture/key-concepts.md b/docs/explanations/architecture/key-concepts.md index b246724e5d5838..3af2d6d87a077d 100644 --- a/docs/explanations/architecture/key-concepts.md +++ b/docs/explanations/architecture/key-concepts.md @@ -26,11 +26,13 @@ Blocks can be static or dynamic. Static blocks contain rendered content and an o Each block contains Attributes or configuration settings, which can be sourced from raw HTML in the content via meta or other customizable origins. -### Transformations +More on [Data format and data flow](/docs/explanations/architecture/data-flow.md). -Blocks have the ability to be transformed into other block types. This allows basic operations like converting a paragraph into a heading, but also more intricate ones like multiple images becoming a gallery. Transformations work for single blocks and for multi-block selections. Internal block variations are also possible transformation targets. +### Block Transforms -### Variations +Blocks have the ability to be transformed into other block types. This allows basic operations like converting a paragraph into a heading, but also more intricate ones like multiple images becoming a gallery. Block transforms work for single blocks and for multi-block selections. Internal block variations are also possible transformation targets. + +### Block Variations Given a block type, a block variation is a predefined set of its initial attributes. This API allows creating a single block from which multiple configurations are possible. Variations provide different possible interfaces, including showing up as entirely new blocks in the library, or as presets when inserting a new block. Read [the API documentation](/docs/reference-guides/block-api/block-registration.md#variations-optional) for more details. @@ -41,24 +43,28 @@ Given a block type, a block variation is a predefined set of its initial attribu ## Reusable Blocks -A reusable blocks is a block (or multiple blocks) that can be inserted and edited globally at once. If a reusable block is edited in one place, those changes are reflected across all posts and pages that block is used. Examples of reusable blocks include a block consisting of a heading whose content and a custom color that would be appear on multiple pages of the site and sidebar widgets that would appear on every page. +A reusable blocks is **an instance** of a block (or multiple blocks) that can be inserted and edited in multiples places, remaining in sync everywhere. If a reusable block is edited in one place, those changes are reflected across all posts and pages that block is used. Examples of reusable blocks include a block consisting of a heading whose content and a custom color that would be appear on multiple pages of the site and sidebar widgets that would appear on every page. Any edits to a reusable block will appear on every other use of that block, saving time from having to make the same edit on different posts. -In technical details, reusable blocks are stored as a hidden post type (`wp_block`) and are dynamic blocks that "ref" or reference the `post_id` and return the `post_content` for that block. +Internally, reusable blocks are stored as a hidden post type (`wp_block`) and are dynamic blocks that "ref" or reference the `post_id` and return the `post_content` for that block. ## Patterns -A [block pattern](/docs/reference-guides/block-api/block-patterns.md) is a group of blocks that have been combined together creating a design pattern. These design patterns provide a starting point for building more advanced pages and layouts quickly. A block pattern can be as small as a single block or as large as a full page of content. Unlike reusable blocks, once a pattern is inserted it doesn't remain in sync with the original content as the blocks contained are meant to be edited and customized by the user. Underneath the surface, patterns are just regular blocks composed together. Themes can register patterns to offer users quick starting points with a design language familiar to that theme's aesthetics. +A [block pattern](/docs/reference-guides/block-api/block-patterns.md) is a group of blocks that have been combined together creating a design pattern. These design patterns provide a starting point for building more advanced pages and layouts quickly, instead of inserting individual blocks. A block pattern can be as small as a single block or as large as a full page of content. Unlike reusable blocks, once a pattern is inserted it doesn't remain in sync with the original content as the blocks contained are meant to be edited and customized by the user. Underneath the surface, patterns are just regular blocks composed together. Themes can register patterns to offer users quick starting points with a design language familiar to that theme's aesthetics. + +## Templates -## Templates (in progress) +While the post editor concentrates on the content of a post, the [template](/docs/reference-guides/block-api/block-templates.md) editor allows declaring and editing an entire site using blocks, from header to footer. Templates are broken down between templates (that describe a full page) and template parts (that describe reusable areas within a template, including semantic areas like header, sidebar, and footer). -While the post editor concentrates on the content of a post, the [template](/docs/reference-guides/block-api/block-templates.md) editor allows declaring and editing an entire site using blocks, from header to footer. To support these efforts there's a collection of blocks that interact with different parts of a site (like the site title, description, logo, navigation, etc) as well as semantic areas like header, sidebar, and footer. Templates are broken down between templates (that describe a full page) and template parts (that describe reusable areas within a template). These templates and template parts can be composed together and registered by a theme. They are also entirely editable by users using the block editor. Customized templates are saved in a `wp_template` post type. Block templates include both static pages and dynamic ones, like archives, singular, home, 404, etc. +These templates and template parts can be composed together and registered by a theme. They are also entirely editable by users using the block editor; a collection of blocks that interact with different properties and settings of the site (like the site title, description, logo, navigation, etc) are especially useful when editing templates and template parts. Customized templates are saved in a `wp_template` post type. Block templates include both static pages and dynamic ones, like archives, singular, home, 404, etc. Note: custom post types can also be initialized with a starting `post_content` template that should not be confused with the theme template system described above. -## Global Styles +More on [Site editing templates](/docs/explanations/architecture/full-site-editing-templates.md). + +## Styles -Global Styles is both an interface (which users access through the site editor) and a configuration system done through [a `theme.json` file](/docs/how-to-guides/themes/theme-json.md). This file absorbs most of the configuration aspects usually scattered through various `add_theme_support` calls to simplify communicating with the editor. It thus aims to improve declaring what settings should be enabled, what specific tools a theme offers (like a custom color palette), the available design tools present, and an infrastructure that allows to coordinate the styles coming from WordPress, the active theme, and the user. +Styles, formerly known as Global Styles and as such referenced in the code, is both an interface that users access through the editor and a configuration system done through [a `theme.json` file](/docs/how-to-guides/themes/theme-json.md). This file absorbs most of the configuration aspects usually scattered through various `add_theme_support` calls to simplify communicating with the editor. It thus aims to improve declaring what settings should be enabled, what specific tools a theme offers (like a custom color palette), the available design tools present, and an infrastructure that allows to coordinate the styles coming from WordPress, the active theme, and the user. Learn more about [Global Styles](/docs/explanations/architecture/styles.md#global-styles). diff --git a/docs/explanations/architecture/performance.md b/docs/explanations/architecture/performance.md index 94848d1386d980..b57a2bba1db615 100644 --- a/docs/explanations/architecture/performance.md +++ b/docs/explanations/architecture/performance.md @@ -6,9 +6,9 @@ Performance is a key feature for editor applications and the Block editor is not To ensure the block editor stays performant across releases and development, we monitor some key metrics using [performance benchmark job](#the-performance-benchmark-job). -**Loading Time:** The time it takes to load an editor page. This includes time the server takes to respond, times to first paint, first contentful paint, DOM content load complete, load complete and first block render. -**Typing Time:** The time it takes for the browser to respond while typing on the editor. -**Block Selection Time:** The time it takes for the browser to respond after a user selects block. (Inserting a block is also equivalent to selecting a block. Monitoring the selection is sufficient to cover both metrics). +- **Loading Time:** The time it takes to load an editor page. This includes time the server takes to respond, times to first paint, first contentful paint, DOM content load complete, load complete and first block render. +- **Typing Time:** The time it takes for the browser to respond while typing on the editor. +- **Block Selection Time:** The time it takes for the browser to respond after a user selects block. (Inserting a block is also equivalent to selecting a block. Monitoring the selection is sufficient to cover both metrics). ## Key Performance Decisions and Solutions @@ -53,12 +53,12 @@ To achieve that the command first prepares the following folder structure: Once the directory above is in place, the performance command loop over the performance test suites (post editor and site editor) and does the following: - 1- Start the environment for branch1 - 2- Run the performance test for the current suite - 3- Stop the environment for branch1 - 4- Repeat the first 3 steps for all other branches - 5- Repeat the previous 4 steps 3 times. - 6- Compute medians for all the performance metrics of the current suite. +1. Start the environment for `branch1` +2. Run the performance test for the current suite +3. Stop the environment for `branch1` +4. Repeat the first 3 steps for all other branches +5. Repeat the previous 4 steps 3 times. +6. Compute medians for all the performance metrics of the current suite. Once all the test suites are executed, a summary report is printed. diff --git a/docs/explanations/architecture/styles.md b/docs/explanations/architecture/styles.md index 416eef796bec8f..a1ed2c33054bc9 100644 --- a/docs/explanations/architecture/styles.md +++ b/docs/explanations/architecture/styles.md @@ -1,23 +1,28 @@ -## Styles in the block editor +## Styles in the editor This document introduces the main concepts related to styles that affect the user content in the block editor. It points to the relevant reference guides and tutorials for readers to dig deeper into each one of the ideas presented. It's aimed to block authors and people working in the block editor project. 1. [HTML and CSS](#html-and-css) 2. [Block styles](#block-styles) - - [From UI controls to HTML markup](#from-ui-controls-to-html-markup) - - [Block Supports API](#block-supports-api) - - [Current limitations of the Block Supports API](#current-limitations-of-the-block-supports-api) + +- [From UI controls to HTML markup](#from-ui-controls-to-html-markup) +- [Block Supports API](#block-supports-api) +- [Current limitations of the Block Supports API](#current-limitations-of-the-block-supports-api) + 3. [Global styles](#global-styles) - - [Gather data](#gather-data) - - [Consolidate data](#consolidate-data) - - [From data to styles](#from-data-to-styles) - - [Current limitations of the Global Styles API](#current-limitations-of-the-global-styles-api) + +- [Gather data](#gather-data) +- [Consolidate data](#consolidate-data) +- [From data to styles](#from-data-to-styles) +- [Current limitations of the Global Styles API](#current-limitations-of-the-global-styles-api) + 4. [Layout styles](#layout-styles) - - [Base layout styles](#base-layout-styles) - - [Individual layout styles](#individual-layout-styles) - - [Available layout types](#available-layout-types) - - [Targeting layout or container blocks from themes](#targeting-layout-or-container-blocks-from-themes) - - [Opting out of generated layout styles](#opting-out-of-generated-layout-styles) + +- [Base layout styles](#base-layout-styles) +- [Individual layout styles](#individual-layout-styles) +- [Available layout types](#available-layout-types) +- [Targeting layout or container blocks from themes](#targeting-layout-or-container-blocks-from-themes) +- [Opting out of generated layout styles](#opting-out-of-generated-layout-styles) ### HTML and CSS @@ -524,16 +529,16 @@ Common layout definitions are stored in [the core `theme.json` file](https://git 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): -- 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/). +- 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: -* 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. +- 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. 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). @@ -551,17 +556,17 @@ Work is currently underway to expand stable semantic classnames in Layout block The current semantic class names that can be output by the Layout block support are: -* `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. -* `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`. -* `is-content-justification-left`: When a block explicitly sets `justifyContent` to `left`. -* `is-content-justification-center`: When a block explicitly sets `justifyContent` to `center`. -* `is-content-justification-right`: When a block explicitly sets `justifyContent` to `right`. -* `is-content-justification-space-between`: When a block explicitly sets `justifyContent` to `space-between`. -* `is-nowrap`: When a block explicitly sets `flexWrap` to `nowrap`. +- `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. +- `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`. +- `is-content-justification-left`: When a block explicitly sets `justifyContent` to `left`. +- `is-content-justification-center`: When a block explicitly sets `justifyContent` to `center`. +- `is-content-justification-right`: When a block explicitly sets `justifyContent` to `right`. +- `is-content-justification-space-between`: When a block explicitly sets `justifyContent` to `space-between`. +- `is-nowrap`: When a block explicitly sets `flexWrap` to `nowrap`. #### Opting out of generated layout styles diff --git a/docs/explanations/faq.md b/docs/explanations/faq.md index f9c6b059e133d9..4a365ce5b41d19 100644 --- a/docs/explanations/faq.md +++ b/docs/explanations/faq.md @@ -151,7 +151,7 @@ This is the canonical list of keyboard shortcuts: Z - Show or hide the settings sidebar. + Show or hide the Settings sidebar. Ctrl+Shift+, , diff --git a/docs/getting-started/outreach.md b/docs/getting-started/outreach.md index 3bfb29d595bc0d..9b51854f58926f 100644 --- a/docs/getting-started/outreach.md +++ b/docs/getting-started/outreach.md @@ -81,6 +81,7 @@ Talks given about Gutenberg, including slides and videos as they are available. You can access all courses [here](https://learn.wordpress.org/). +- [Intro to Block Development: Build Your First Custom Block](https://learn.wordpress.org/course/introduction-to-block-development-build-your-first-custom-block/), Jonathan Bossenger (2023) - [Registering Block Patterns](https://learn.wordpress.org/workshop/registering-block-patterns/), Daisy Olsen (January 2021) - [Intro to Gutenberg Block Development](https://learn.wordpress.org/workshop/intro-to-gutenberg-block-development/), Jonathan Bossenger (August 2020) - [Intro to Publishing with the Block Editor](https://learn.wordpress.org/workshop/intro-to-publishing-with-the-block-editor/), Erica Varlese (August 2020) diff --git a/docs/how-to-guides/block-tutorial/writing-your-first-block-type.md b/docs/how-to-guides/block-tutorial/writing-your-first-block-type.md index a65a9473d5e1d3..83a3cd156251bf 100644 --- a/docs/how-to-guides/block-tutorial/writing-your-first-block-type.md +++ b/docs/how-to-guides/block-tutorial/writing-your-first-block-type.md @@ -6,7 +6,9 @@ This guide takes you through creating a basic block to display a message in a po There are two main types of blocks: static and dynamic, this guide focuses on static blocks. A static block is used to insert HTML content into the post and save it with the post. A dynamic block builds the content on the fly when rendered on the front end, see the [dynamic blocks guide](/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md). +
This guide focuses on just the block, see the [Create a Block tutorial](/docs/getting-started/create-block/README.md) for a complete setup. +
## Before you start @@ -141,6 +143,7 @@ In order to register the block, an asset php file is required in the same direct {% JSX %} Build the scripts and asset file which is used to keep track of dependencies and the build version. + ```bash npm run build ``` diff --git a/docs/how-to-guides/curating-the-editor-experience.md b/docs/how-to-guides/curating-the-editor-experience.md index 02f9cfba765d88..b35911d69c6027 100644 --- a/docs/how-to-guides/curating-the-editor-experience.md +++ b/docs/how-to-guides/curating-the-editor-experience.md @@ -20,7 +20,7 @@ Alongside the ability to lock moving or removing blocks, the [Navigation Block]( **Apply block locking to patterns or templates** -When building patterns or templates, theme authors can use these same UI tools to set the default locked state of blocks. For example, a theme author could lock various pieces of a header. Keep in mind that by default, users with editing access can unlock these blocks. [Here’s an example of a pattern](https://gist.github.com/annezazu/acee30f8b6e8995e1b1a52796e6ef805) with various blocks locked in different ways and here’s more context on [creating a template with locked blocks](https://make.wordpress.org/core/2022/02/09/core-editor-improvement-curated-experiences-with-locking-apis-theme-json/). You can build these patterns in the editor itself, including adding locking options, before following the [documentation to register them](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-patterns/). +When building patterns or templates, theme authors can use these same UI tools to set the default locked state of blocks. For example, a theme author could lock various pieces of a header. Keep in mind that by default, users with editing access can unlock these blocks. [Here’s an example of a pattern](https://gist.github.com/annezazu/acee30f8b6e8995e1b1a52796e6ef805) with various blocks locked in different ways and here’s more context on [creating a template with locked blocks](https://make.wordpress.org/core/2022/02/09/core-editor-improvement-curated-experiences-with-locking-apis-theme-json/). You can build these patterns in the editor itself, including adding locking options, before following the [documentation to register them](/docs/reference-guides/block-api/block-patterns.md). **Apply content only editing in patterns or templates** @@ -33,7 +33,7 @@ This functionality was introduced in WordPress 6.1. In contrast to block locking - Additional child blocks cannot be inserted, further preserving the design and layout. - There is a link in the block toolbar to ‘Modify’ that a user can toggle on/off to have access to the broader design tools. Currently, it's not possibly to programmatically remove this option. -This option can be applied to Columns, Cover, and Group blocks as well as third-party blocks that have the templateLock attribute in its block.json. To adopt this functionality, you need to use `"templateLock":"contentOnly"`. [Here's an example of a pattern](https://gist.github.com/annezazu/d62acd2514cea558be6cea97fe28ff3c) with this functionality in place. For more information, please [review the relevant documentation](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-templates/#locking). +This option can be applied to Columns, Cover, and Group blocks as well as third-party blocks that have the templateLock attribute in its block.json. To adopt this functionality, you need to use `"templateLock":"contentOnly"`. [Here's an example of a pattern](https://gist.github.com/annezazu/d62acd2514cea558be6cea97fe28ff3c) with this functionality in place. For more information, please [review the relevant documentation](/docs/reference-guides/block-api/block-templates.md#locking). Note: There is no UI in place to manage content locking and it must be managed at the code level. @@ -232,7 +232,7 @@ Continuing the examples with duotone, this means you could allow full access to } ``` -You can read more about how best to [turn on/off options with theme.json here](https://developer.wordpress.org/block-editor/how-to-guides/themes/theme-json/). +You can read more about how best to [turn on/off options with theme.json here](/docs/how-to-guides/themes/theme-json.md). **Disable inherit default layout** @@ -326,7 +326,62 @@ add_filter( 'wp_theme_json_data_theme', 'example_filter_theme_json_data_theme' ) The filter receives an instance of the `WP_Theme_JSON_Data class` with the data for the respective layer. Then, you pass new data in a valid theme.json-like structure to the `update_with( $new_data )` method. A theme.json version number is required in `$new_data`. -Read more about this functionality in the [Filters for theme.json data dev note](https://make.wordpress.org/core/2022/10/10/filters-for-theme-json-data/). + +## Limiting interface options with client-side filters + +WordPress 6.2 introduced a new client-side filter allowing you to modify block-level [theme.json settings](/docs/reference-guides/theme-json-reference/theme-json-living.md#settings) before the editor is rendered. + +The filter is called `blockEditor.useSetting.before` and can be used in the JavaScript code as follows: + +``` +import { addFilter } from '@wordpress/hooks'; + +/** + * Limit the Column block's spacing options to pixels. + */ +addFilter( + 'blockEditor.useSetting.before', + 'example/useSetting.before', + ( settingValue, settingName, clientId, blockName ) => { + if ( blockName === Media & Text block'core/column' && settingName === 'spacing.units' ) { + return [ 'px' ]; + } + return settingValue; + } +); +``` + +This example will restrict the available spacing units for the Column block to just pixels. As discussed above, a similar restriction could be applied using theme.json filters or directly in a theme’s theme.json file using block-level settings. + +However, the `blockEditor.useSetting.before` filter is unique because it allows you to modify settings according to the block’s location, neighboring blocks, the current user’s role, and more. The possibilities for customization are extensive. + +In the following example, text color controls are disabled for the Heading block whenever the block is placed inside of a Media & Text block. + +``` +import { select } from '@wordpress/data'; +import { addFilter } from '@wordpress/hooks'; + +/** + * Disable text color controls on Heading blocks when placed inside of Media & Text blocks. + */ +addFilter( + 'blockEditor.useSetting.before', + 'example/useSetting.before', + ( settingValue, settingName, clientId, blockName ) => { + if ( blockName === 'core/heading' ) { + const { getBlockParents, getBlockName } = select( 'core/block-editor' ); + const blockParents = getBlockParents( clientId, true ); + const inMediaText = blockParents.some( ( ancestorId ) => getBlockName( ancestorId ) === 'core/media-text' ); + + if ( inMediaText && settingName === 'color.text' ) { + return false; + } + } + + return settingValue; + } +); +``` ## Remove access to functionality @@ -340,7 +395,7 @@ This prevents both the ability to both create new block templates or edit them f **Create an allow or disallow list to limit block options** -There might be times when you don’t want access to a block at all to be available for users. To control what’s available in the inserter, you can take two approaches: [an allow list](https://developer.wordpress.org/block-editor/reference-guides/filters/block-filters/#using-an-allow-list) that disables all blocks except those on the list or a [deny list that unregisters specific blocks](https://developer.wordpress.org/block-editor/reference-guides/filters/block-filters/#using-a-deny-list). +There might be times when you don’t want access to a block at all to be available for users. To control what’s available in the inserter, you can take two approaches: [an allow list](/docs/reference-guides/filters/block-filters.md#using-an-allow-list) that disables all blocks except those on the list or a [deny list that unregisters specific blocks](/docs/reference-guides/filters/block-filters.md#using-a-deny-list). **Disable pattern directory** @@ -360,7 +415,7 @@ Read more about this functionality in the [Page creation patterns in WordPress 6 **Lock patterns** -As mentioned in the prior section on Locking APIs, aspects of patterns themselves can be locked so that the important aspects of the design can be preserved. [Here’s an example of a pattern](https://gist.github.com/annezazu/acee30f8b6e8995e1b1a52796e6ef805) with various blocks locked in different ways. You can build these patterns in the editor itself, including adding locking options, before [following the documentation to register them](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-patterns/). +As mentioned in the prior section on Locking APIs, aspects of patterns themselves can be locked so that the important aspects of the design can be preserved. [Here’s an example of a pattern](https://gist.github.com/annezazu/acee30f8b6e8995e1b1a52796e6ef805) with various blocks locked in different ways. You can build these patterns in the editor itself, including adding locking options, before [following the documentation to register them](/docs/reference-guides/block-api/block-patterns.md). **Prioritize specific patterns from the Pattern Directory** @@ -372,7 +427,7 @@ With WordPress 6.0 themes can register patterns from [Pattern Directory](https:/ } ``` -Note that this field requires using [version 2 of theme.json](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/). The content creator will then find the respective Pattern in the inserter “Patterns” tab in the categories that match the categories from the Pattern Directory. +Note that this field requires using [version 2 of theme.json](/docs/reference-guides/theme-json-reference/theme-json-living.md). The content creator will then find the respective Pattern in the inserter “Patterns” tab in the categories that match the categories from the Pattern Directory. ## Combining approaches diff --git a/docs/how-to-guides/data-basics/2-building-a-list-of-pages.md b/docs/how-to-guides/data-basics/2-building-a-list-of-pages.md index 2aa91cd1a067e9..282578fbb66dce 100644 --- a/docs/how-to-guides/data-basics/2-building-a-list-of-pages.md +++ b/docs/how-to-guides/data-basics/2-building-a-list-of-pages.md @@ -247,7 +247,7 @@ Voila! We can now filter the results: Let’s take a pause for a moment to consider the downsides of an alternative approach we could have taken - working with the API directly. Imagine we sent the API requests directly: ```js -import { apiFetch } from '@wordpress/api-fetch'; +import apiFetch from '@wordpress/api-fetch'; function MyFirstApp() { // ... const [pages, setPages] = useState( [] ); diff --git a/docs/how-to-guides/javascript/versions-and-building.md b/docs/how-to-guides/javascript/versions-and-building.md index 257b73c6866f16..ec9f98368ff024 100644 --- a/docs/how-to-guides/javascript/versions-and-building.md +++ b/docs/how-to-guides/javascript/versions-and-building.md @@ -4,8 +4,7 @@ The Block Editor Handbook shows JavaScript examples in two syntaxes: JSX and Pla Plain refers to JavaScript code compatible with WordPress's minimum [target for browser support](https://make.wordpress.org/core/handbook/best-practices/browser-support/) without requiring a transpilation step. This step is commonly referred to as a build process. -"JSX" doesn't refer to a specific version of JavaScript, but refers to the latest language definition plus -[JSX syntax](https://reactjs.org/docs/introducing-jsx.html), a syntax that blends HTML and JavaScript. JSX makes it easier to read and write markup code, but does require a build step to transpile into code compatible with browsers. Webpack and babel are the tools that perform this transpilation step. +"JSX" doesn't refer to a specific version of JavaScript, but refers to the latest language definition plus [JSX syntax](https://reactjs.org/docs/introducing-jsx.html), a syntax that blends HTML and JavaScript. JSX makes it easier to read and write markup code, but does require a build step to transpile into code compatible with browsers. Webpack and babel are the tools that perform this transpilation step. For simplicity, the JavaScript tutorial uses the Plain definition, without JSX. This code can run straight in your browser and does not require an additional build step. In many cases, it is perfectly fine to follow the same approach for simple plugins or experimenting. As your codebase grows in complexity it might be a good idea to switch to JSX. You will find the majority of code and documentation across the block editor uses JSX. diff --git a/docs/how-to-guides/plugin-sidebar-0.md b/docs/how-to-guides/plugin-sidebar-0.md index 306ef3319a0c18..d7e767db93dd75 100644 --- a/docs/how-to-guides/plugin-sidebar-0.md +++ b/docs/how-to-guides/plugin-sidebar-0.md @@ -380,3 +380,31 @@ Functions used in this guide: You now have a custom sidebar that you can use to update `post_meta` content. A complete example is available, download the [plugin-sidebar example](https://github.com/WordPress/gutenberg-examples/tree/trunk/blocks-non-jsx/plugin-sidebar) from the [gutenberg-examples](https://github.com/WordPress/gutenberg-examples) repository. + +### Note + +If you have enabled Custom Fields in the 'Panels' page of the Editor 'Preferences' (via the three dots in top right), a field with the same name as the TextControl, in this case `sidebar_plugin_meta_block_field`, will also appear in the custom fields panel at the bottom of the editor window. These two fields have access to the same meta property. + +![Text Control and Custom Field](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/plugin-sidebar-text-control-custom-field.png) + +On saving the post the value in the TextControl will be saved first and the value in the custom field will be saved second, so that is the one that ends up persisting in the database. So if you change the value in the TextControl it is still the one in the custom field that ends up getting saved. + +This problem does not exist if Custom Fields is not enabled. + +If you need to have Custom Fields enabled and also have post meta in the sidebar there are two possible solutions: + +1. Precede the name of the meta field with an underscore, so the name in the above example would be `_sidebar_plugin_meta_block_field`. This indicates that the post meta should be treated as private so it will not be visible in the Custom Fields section of a post. With this solution an error will be generated when you save the post unless you add an `auth_callback` property to the `args` array passed to `register_post_meta` with a function that ultimately returns `true`. See the `args` documentation in the [post_meta](https://developer.wordpress.org/reference/functions/register_meta/#parameters) page for more info. +2. In the TextControl's `onChange` function, target the Value field textarea and set the value there to be the same as the value in the TextControl meta field. The value will then be identical in both places and so you can be assured that if the value is changed in the TextControl then it will still be saved to the database. + +```js +return el( TextControl, { + label: 'Meta Block Field', + value: metaFieldValue, + onChange: function ( content ) { + editPost( { + meta: { sidebar_plugin_meta_block_field: content } + }) + document.querySelector( {the-value-textarea} ).innerHTML = content; + }, +} ); +``` \ No newline at end of file diff --git a/docs/how-to-guides/propagating-updates.md b/docs/how-to-guides/propagating-updates.md index 3bb281fe6fdfea..ae5cb5f3b2a9ac 100644 --- a/docs/how-to-guides/propagating-updates.md +++ b/docs/how-to-guides/propagating-updates.md @@ -55,7 +55,7 @@ Likewise, it is generally poor practice to delete template parts in theme update ### Resources -- [Comparing Patterns, Template Parts, and Reusable Blocks](https://wordpress.org/support/article/comparing-patterns-template-parts-and-reusable-blocks/) +- [Comparing Patterns, Template Parts, and Reusable Blocks](https://wordpress.org/documentation/article/comparing-patterns-template-parts-and-reusable-blocks/) - [Block deprecation](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-deprecation/) - [Create Block tool](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/) diff --git a/docs/how-to-guides/themes/theme-json.md b/docs/how-to-guides/themes/theme-json.md index 8e2b60a7693eb6..4a3dc8f70a85e6 100644 --- a/docs/how-to-guides/themes/theme-json.md +++ b/docs/how-to-guides/themes/theme-json.md @@ -18,6 +18,7 @@ WordPress 5.8 comes with [a new mechanism](https://make.wordpress.org/core/2021/ - Top-level - Block-level - Elements + - Variations - customTemplates - templateParts - patterns @@ -289,6 +290,7 @@ The settings section has the following structure: "fontWeight": true, "letterSpacing": true, "lineHeight": false, + "textColumns": false, "textDecoration": true, "textTransform": true }, @@ -321,6 +323,7 @@ There's one special setting property, `appearanceTools`, which is a boolean and - border: color, radius, style, width - color: link - dimensions: minHeight +- position: sticky - spacing: blockGap, margin, padding - typography: lineHeight @@ -824,6 +827,7 @@ Each block declares which style properties it exposes via the [block supports me "fontWeight": "value", "letterSpacing": "value", "lineHeight": "value", + "textColumns": "value", "textDecoration": "value", "textTransform": "value" }, @@ -1064,7 +1068,7 @@ h3 { ##### Element pseudo selectors -Pseudo selectors `:hover`, `:focus`, `:visited` are supported by Gutenberg. +Pseudo selectors `:hover`, `:focus`, `:visited`, `:active`, `:link`, `:any-link` are supported by Gutenberg. ```json "elements": { @@ -1081,6 +1085,41 @@ Pseudo selectors `:hover`, `:focus`, `:visited` are supported by Gutenberg. } ``` +#### Variations + +A block can have a "style variation", as defined per the [block.json specification](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/#styles-optional). Theme authors can define the style attributes for an existing style variation using the theme.json file. Styles for unregistered style variations will be ignored. + +Note that variations are a "block concept", they only exist bound to blocks. The `theme.json` specification respects that distinction by only allowing `variations` at the block-level but not at the top-level. It's also worth highlighting that only variations defined in the `block.json` file of the block are considered "registered": so far, the style variations added via `register_block_style` or in the client are ignored, see [this issue](https://github.com/WordPress/gutenberg/issues/49602) for more information. + +For example, this is how to provide styles for the existing `plain` variation for the `core/quote` block: + +```json +{ + "version": 2, + "styles":{ + "blocks": { + "core/quote": { + "variations": { + "plain": { + "color": { + "background": "red" + } + } + } + } + } + } +} +``` + +The resulting CSS output is this: + +```css +.wp-block-quote.is-style-plain { + background-color: red; +} +``` + ### customTemplates
Supported in WordPress from version 5.9.
@@ -1135,7 +1174,7 @@ Currently block variations exist for "header" and "footer" values of the area te ### patterns -
Supported in WordPress from version 6.0 using [version 2](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/) of `theme.json`.
+
Supported in WordPress from version 6.0 using version 2 of theme.json.
Within this field themes can list patterns to register from [Pattern Directory](https://wordpress.org/patterns/). The `patterns` field is an array of pattern `slugs` from the Pattern Directory. Pattern slugs can be extracted by the `url` in single pattern view at the Pattern Directory. For example in this url `https://wordpress.org/patterns/pattern/partner-logos` the slug is `partner-logos`. @@ -1319,4 +1358,4 @@ The value defined for the root `styles.spacing.blockGap` style is also output as ### Why does it take so long to update the styles in the browser? -When you are actively developing with theme.json you may notice it takes 30+ seconds for your changes to show up in the browser, this is because `theme.json` is cached. To remove this caching issue, set either [`WP_DEBUG`](https://wordpress.org/support/article/debugging-in-wordpress/#wp_debug) or [`SCRIPT_DEBUG`](https://wordpress.org/support/article/debugging-in-wordpress/#script_debug) to 'true' in your [`wp-config.php`](https://wordpress.org/support/article/editing-wp-config-php/). This tells WordPress to skip the cache and always use fresh data. +When you are actively developing with theme.json you may notice it takes 30+ seconds for your changes to show up in the browser, this is because `theme.json` is cached. To remove this caching issue, set either [`WP_DEBUG`](https://wordpress.org/documentation/article/debugging-in-wordpress/#wp_debug) or [`SCRIPT_DEBUG`](https://wordpress.org/documentation/article/debugging-in-wordpress/#script_debug) to 'true' in your [`wp-config.php`](https://wordpress.org/documentation/article/editing-wp-config-php/). This tells WordPress to skip the cache and always use fresh data. diff --git a/docs/manifest.json b/docs/manifest.json index d7aa98b85a4cfa..8b6677d39c5591 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -437,6 +437,12 @@ "markdown_source": "../docs/reference-guides/block-api/block-registration.md", "parent": "block-api" }, + { + "title": "Selectors", + "slug": "block-selectors", + "markdown_source": "../docs/reference-guides/block-api/block-selectors.md", + "parent": "block-api" + }, { "title": "Styles", "slug": "block-styles", @@ -450,7 +456,7 @@ "parent": "block-api" }, { - "title": "Block Templates", + "title": "Templates", "slug": "block-templates", "markdown_source": "../docs/reference-guides/block-api/block-templates.md", "parent": "block-api" @@ -605,6 +611,12 @@ "markdown_source": "../docs/reference-guides/theme-json-reference/theme-json-migrations.md", "parent": "theme-json-reference" }, + { + "title": "Available Styles Options", + "slug": "styles-versions", + "markdown_source": "../docs/reference-guides/theme-json-reference/styles-versions.md", + "parent": "theme-json-reference" + }, { "title": "Component Reference", "slug": "components", @@ -1463,6 +1475,12 @@ "markdown_source": "../packages/browserslist-config/README.md", "parent": "packages" }, + { + "title": "@wordpress/commands", + "slug": "packages-commands", + "markdown_source": "../packages/commands/README.md", + "parent": "packages" + }, { "title": "@wordpress/components", "slug": "packages-components", @@ -1475,6 +1493,12 @@ "markdown_source": "../packages/compose/README.md", "parent": "packages" }, + { + "title": "@wordpress/core-commands", + "slug": "packages-core-commands", + "markdown_source": "../packages/core-commands/README.md", + "parent": "packages" + }, { "title": "@wordpress/core-data", "slug": "packages-core-data", @@ -1499,12 +1523,6 @@ "markdown_source": "../packages/create-block/docs/external-template.md", "parent": "packages-create-block" }, - { - "title": "@wordpress/custom-templated-path-webpack-plugin", - "slug": "packages-custom-templated-path-webpack-plugin", - "markdown_source": "../packages/custom-templated-path-webpack-plugin/README.md", - "parent": "packages" - }, { "title": "@wordpress/customize-widgets", "slug": "packages-customize-widgets", @@ -1559,6 +1577,12 @@ "markdown_source": "../packages/dom/README.md", "parent": "packages" }, + { + "title": "@wordpress/e2e-test-utils-playwright", + "slug": "packages-e2e-test-utils-playwright", + "markdown_source": "../packages/e2e-test-utils-playwright/README.md", + "parent": "packages" + }, { "title": "@wordpress/e2e-test-utils", "slug": "packages-e2e-test-utils", @@ -1697,12 +1721,6 @@ "markdown_source": "../packages/lazy-import/README.md", "parent": "packages" }, - { - "title": "@wordpress/library-export-default-webpack-plugin", - "slug": "packages-library-export-default-webpack-plugin", - "markdown_source": "../packages/library-export-default-webpack-plugin/README.md", - "parent": "packages" - }, { "title": "@wordpress/list-reusable-blocks", "slug": "packages-list-reusable-blocks", @@ -1817,6 +1835,12 @@ "markdown_source": "../packages/rich-text/README.md", "parent": "packages" }, + { + "title": "@wordpress/router", + "slug": "packages-router", + "markdown_source": "../packages/router/README.md", + "parent": "packages" + }, { "title": "@wordpress/scripts", "slug": "packages-scripts", @@ -2034,7 +2058,7 @@ "parent": "architecture" }, { - "title": "Full Site Editing Templates", + "title": "Site Editing Templates", "slug": "full-site-editing-templates", "markdown_source": "../docs/explanations/architecture/full-site-editing-templates.md", "parent": "architecture" @@ -2256,7 +2280,7 @@ "parent": "contributors" }, { - "title": "Localizing Gutenberg Plugin", + "title": "Localizing Gutenberg", "slug": "localizing", "markdown_source": "../docs/contributors/localizing.md", "parent": "contributors" diff --git a/docs/reference-guides/block-api/README.md b/docs/reference-guides/block-api/README.md index e2de21d99f85e0..128b9abf0f0d7d 100644 --- a/docs/reference-guides/block-api/README.md +++ b/docs/reference-guides/block-api/README.md @@ -4,16 +4,18 @@ Blocks are the fundamental element of the editor. They are the primary way in wh The following sections will walk you through the existing block APIs: -- [API Versions](/docs/reference-guides/block-api/block-api-versions.md) - [Annotations](/docs/reference-guides/block-api/block-annotations.md) +- [API Versions](/docs/reference-guides/block-api/block-api-versions.md) - [Attributes](/docs/reference-guides/block-api/block-attributes.md) - [Context](/docs/reference-guides/block-api/block-context.md) - [Deprecation](/docs/reference-guides/block-api/block-deprecation.md) - [Edit and Save](/docs/reference-guides/block-api/block-edit-save.md) +- [Metadata in block.json](/docs/reference-guides/block-api/block-metadata.md) - [Patterns](/docs/reference-guides/block-api/block-patterns.md) - [Registration](/docs/reference-guides/block-api/block-registration.md) +- [Selectors](/docs/reference-guides/block-api/block-selectors.md) +- [Styles](/docs/reference-guides/block-api/block-styles.md) - [Supports](/docs/reference-guides/block-api/block-supports.md) - [Transformations](/docs/reference-guides/block-api/block-transforms.md) - [Templates](/docs/reference-guides/block-api/block-templates.md) -- [Metadata](/docs/reference-guides/block-api/block-metadata.md) - [Variations](/docs/reference-guides/block-api/block-variations.md) diff --git a/docs/reference-guides/block-api/block-annotations.md b/docs/reference-guides/block-api/block-annotations.md index b24beddb7af465..89f64b43fa3b92 100644 --- a/docs/reference-guides/block-api/block-annotations.md +++ b/docs/reference-guides/block-api/block-annotations.md @@ -13,7 +13,7 @@ To see the API for yourself the easiest way is to have a block that is at least ```js wp.data.dispatch( 'core/annotations' ).addAnnotation( { source: 'my-annotations-plugin', - blockClientId: wp.data.select( 'core/editor' ).getBlockOrder()[ 0 ], + blockClientId: wp.data.select( 'core/block-editor' ).getBlockOrder()[ 0 ], richTextIdentifier: 'content', range: { start: 50, @@ -43,7 +43,7 @@ It is also possible to annotate a block completely. In that case just provide th ```js wp.data.dispatch( 'core/annotations' ).addAnnotation( { source: 'my-annotations-plugin', - blockClientId: wp.data.select( 'core/editor' ).getBlockOrder()[ 0 ], + blockClientId: wp.data.select( 'core/block-editor' ).getBlockOrder()[ 0 ], selector: 'block', } ); ``` diff --git a/docs/reference-guides/block-api/block-context.md b/docs/reference-guides/block-api/block-context.md index 0dc32fb0d88562..3ab3175a0d1b23 100644 --- a/docs/reference-guides/block-api/block-context.md +++ b/docs/reference-guides/block-api/block-context.md @@ -26,7 +26,9 @@ A block can provide a context value by assigning a `providesContext` property in }, ``` -For complete example, refer below section. +For complete example, refer to the section below. + +#### Include a namespace As seen in the above example, it is recommended that you include a namespace as part of the name of the context key so as to avoid potential conflicts with other plugins or default context values provided by WordPress. The context namespace should be specific to your plugin, and in most cases can be the same as used in the name of the block itself. @@ -124,7 +126,7 @@ registerBlockType( 'my-plugin/record-title', { } ); ``` -3. Edit `src/edit.js`. Replace `Edit` function by following code. +3. Edit `src/edit.js` for the `record` block. Replace `Edit` function by following code. ```js import { TextControl } from '@wordpress/components'; @@ -151,7 +153,7 @@ export default function Edit( props ) { } ``` -4. Edit `src/save.js`. Replace `save` function by following code. +4. Edit `src/save.js` for the `record` block. Replace `save` function by following code. ```js export default function save( props ) { @@ -159,6 +161,6 @@ export default function save( props ) { } ``` -5. Create new post and add `record` block. If you type number in the above box, you'll see the same number is shown in below box. +5. Create new post and add the `record` block. If you type a number in the text box, you'll see the same number is shown in the `record-title` block below it. ![Block Context Example](https://user-images.githubusercontent.com/8876600/93000215-c8570380-f561-11ea-9bd0-0b2bd0ca1752.png) diff --git a/docs/reference-guides/block-api/block-deprecation.md b/docs/reference-guides/block-api/block-deprecation.md index 0314939ee8f649..d40c13c0406124 100644 --- a/docs/reference-guides/block-api/block-deprecation.md +++ b/docs/reference-guides/block-api/block-deprecation.md @@ -1,5 +1,7 @@ # Deprecation +> This page provides a comprehensive guide to the principles and usage of the Deprecation API. For an introduction check out the [tutorial on the basics of block deprecation](https://developer.wordpress.org/news/2023/03/block-deprecation-a-tutorial/) which can be found on the [Developer Blog](https://developer.wordpress.org/news/). + When updating static blocks markup and attributes, block authors need to consider existing posts using the old versions of their block. To provide a good upgrade path, you can choose one of the following strategies: - Do not deprecate the block and create a new one (a different name) @@ -37,10 +39,25 @@ Deprecations are defined on a block type as its `deprecated` property, an array - `attributes` (Object): The [attributes definition](/docs/reference-guides/block-api/block-attributes.md) of the deprecated form of the block. - `supports` (Object): The [supports definition](/docs/reference-guides/block-api/block-registration.md) of the deprecated form of the block. - `save` (Function): The [save implementation](/docs/reference-guides/block-api/block-edit-save.md) of the deprecated form of the block. -- `migrate` (Function, Optional): A function which, given the old attributes and inner blocks is expected to return either the new attributes or a tuple array of `[ attributes, innerBlocks ]` compatible with the block. As mentioned above, a deprecation's `migrate` will not be run if its `save` function does not return a valid block so you will need to make sure your migrations are available in all the deprecations where they are relevant. -- `isEligible` (Function, Optional): A function which, given the attributes and inner blocks of the parsed block, returns true if the deprecation can handle the block migration even if the block is valid. This is particularly useful in cases where a block is technically valid even once deprecated, but still requires updates to its attributes or inner blocks. This function is not called when the results of all previous deprecations' `save` functions were invalid. - -It's important to note that `attributes`, `supports`, and `save` are not automatically inherited from the current version, since they can impact parsing and serialization of a block, so they must be defined on the deprecated object in order to be processed during a migration. +- `migrate`: (Function, Optional). A function which, given the old attributes and inner blocks is expected to return either the new attributes or a tuple array of attributes and inner blocks compatible with the block. As mentioned above, a deprecation's `migrate` will not be run if its `save` function does not return a valid block so you will need to make sure your migrations are available in all the deprecations where they are relevant. + - _Parameters_ + - `attributes`: The block's old attributes. + - `innerBlocks`: The block's old inner blocks. + - _Return_ + - `Object | Array`: Either the updated block attributes or tuple array `[attributes, innerBlocks]`. +- `isEligible`: (Function, Optional). A function which returns `true` if the deprecation can handle the block migration even if the block is valid. It is particularly useful in cases where a block is technically valid even once deprecated, but still requires updates to its attributes or inner blocks. This function is **not** called when the results of all previous deprecations' save functions were invalid. + - _Parameters_ + - `attributes`: The raw block attributes as parsed from the serialized HTML, and before the block type code is applied. + - `innerBlocks`: The block's current inner blocks. + - `data`: An object containing properties representing the block node and its resulting block object. + - `data.blockNode`: The raw form of the block as a result of parsing the serialized HTML. + - `data.block`: The block object, which is the result of applying the block type to the `blockNode`. + - _Return_ + - `boolean`: Whether or not this otherwise valid block is eligible to be migrated by this deprecation. + +
+It's important to note that attributes, supports, and save are not automatically inherited from the current version, since they can impact parsing and serialization of a block, so they must be defined on the deprecated object in order to be processed during a migration. +
### Example: @@ -63,7 +80,7 @@ registerBlockType( 'gutenberg/block-with-deprecated-version', { // ... other block properties go here attributes, - + supports, save( props ) { @@ -73,7 +90,7 @@ registerBlockType( 'gutenberg/block-with-deprecated-version', { deprecated: [ { attributes, - + supports, save( props ) { diff --git a/docs/reference-guides/block-api/block-edit-save.md b/docs/reference-guides/block-api/block-edit-save.md index 5d660992f6abb9..f585d734547277 100644 --- a/docs/reference-guides/block-api/block-edit-save.md +++ b/docs/reference-guides/block-api/block-edit-save.md @@ -1,6 +1,6 @@ # Edit and Save -When registering a block, the `edit` and `save` functions provide the interface for how a block is going to be rendered within the editor, how it will operate and be manipulated, and how it will be saved. +When registering a block with JavaScript on the client, the `edit` and `save` functions provide the interface for how a block is going to be rendered within the editor, how it will operate and be manipulated, and how it will be saved. ## Edit diff --git a/docs/reference-guides/block-api/block-metadata.md b/docs/reference-guides/block-api/block-metadata.md index d78050d79a9a1e..9bfdce9279ff24 100644 --- a/docs/reference-guides/block-api/block-metadata.md +++ b/docs/reference-guides/block-api/block-metadata.md @@ -1,6 +1,6 @@ # Metadata in block.json -Starting in WordPress 5.8 release, we encourage using the `block.json` metadata file as the canonical way to register block types. Here is an example `block.json` file that would define the metadata for a plugin create a notice block. +Starting in WordPress 5.8 release, we recommend using the `block.json` metadata file as the canonical way to register block types with both PHP (server-side) and JavaScript (client-side). Here is an example `block.json` file that would define the metadata for a plugin create a notice block. **Example:** @@ -28,6 +28,9 @@ Starting in WordPress 5.8 release, we encourage using the `block.json` metadata "my-plugin/message": "message" }, "usesContext": [ "groupId" ], + "selectors": { + "root": ".wp-block-my-plugin-notice" + }, "supports": { "align": true }, @@ -237,7 +240,7 @@ Setting `parent` lets a block require that it is only available when nested with { "ancestor": [ "my-block/product" ] } ``` -The `ancestor` property makes a block available inside the specified block types at any position of the ancestor block subtree. That allows, for example, to place a ‘Comment Content’ block inside a ‘Column’ block, as long as ‘Column’ is somewhere within a ‘Comment Template’ block. In comparrison to the `parent` property blocks that specify their `ancestor` can be placed anywhere in the subtree whilst blocks with a specified `parent` need to be direct children. +The `ancestor` property makes a block available inside the specified block types at any position of the ancestor block subtree. That allows, for example, to place a ‘Comment Content’ block inside a ‘Column’ block, as long as ‘Column’ is somewhere within a ‘Comment Template’ block. In comparison to the `parent` property blocks that specify their `ancestor` can be placed anywhere in the subtree whilst blocks with a specified `parent` need to be direct children. ### Icon @@ -379,6 +382,38 @@ See [the block context documentation](/docs/reference-guides/block-api/block-con } ``` +### Selectors + +- Type: `object` +- Optional +- Localized: No +- Property: `selectors` +- Default: `{}` +- Since: `WordPress 6.3.0` + +Any custom CSS selectors, keyed by `root`, feature, or sub-feature, to be used +when generating block styles for theme.json (global styles) stylesheets. +Providing custom selectors allows more fine grained control over which styles +apply to what block elements, e.g. applying typography styles only to an inner +heading while colors are still applied on the outer block wrapper etc. + +See the [the selectors documentation](/docs/reference-guides/block-api/block-selectors.md) for more details. + +```json +{ + "selectors": { + "root": ".my-custom-block-selector", + "color": { + "text": ".my-custom-block-selector p" + }, + "typography": { + "root": ".my-custom-block-selector > h2", + "text-decoration": ".my-custom-block-selector > h2 span" + } + } +} +``` + ### Supports - Type: `object` diff --git a/docs/reference-guides/block-api/block-patterns.md b/docs/reference-guides/block-api/block-patterns.md index d58e7a2f62d41c..68e47d50942372 100644 --- a/docs/reference-guides/block-api/block-patterns.md +++ b/docs/reference-guides/block-api/block-patterns.md @@ -193,7 +193,7 @@ register_block_pattern( ); ``` -In the above example if we select **one of the two** block types, either a paragraph or a heading block, this pattern will be suggested by transforming the selected block using its content and will also add the remaing blocks from the pattern. If on the other hand we multi select one paragraph and one heading block, both blocks will be transformed. +In the above example if we select **one of the two** block types, either a paragraph or a heading block, this pattern will be suggested by transforming the selected block using its content and will also add the remaining blocks from the pattern. If on the other hand we multi select one paragraph and one heading block, both blocks will be transformed. Blocks can also use these contextual block patterns in other places. For instance, when inserting a new Query Loop block, the user is provided with a list of all patterns attached to the block. diff --git a/docs/reference-guides/block-api/block-registration.md b/docs/reference-guides/block-api/block-registration.md index aed44f393b3717..cecdb663e3d607 100644 --- a/docs/reference-guides/block-api/block-registration.md +++ b/docs/reference-guides/block-api/block-registration.md @@ -2,7 +2,11 @@ Block registration API reference. -**Note:** You can use the functions documented on this page to register a block on the client-side only, but a flexible method to register new block types is to use the `block.json` metadata file. See [metadata documentation for complete information](/docs/reference-guides/block-api/block-metadata.md). +
+You can use the functions documented on this page to register a block with JavaScript only on the client, but the recommended method is to register new block types also with PHP on the server using the `block.json` metadata file. See metadata documentation for complete information +
+Learn how to create your first block for the WordPress block editor. From setting up your development environment, tools, and getting comfortable with the new development model, this tutorial covers all you need to know to get started with creating blocks. +
## `registerBlockType` @@ -264,7 +268,6 @@ parent: [ 'core/columns' ], The `ancestor` property makes a block available inside the specified block types at any position of the ancestor block subtree. That allows, for example, to place a 'Comment Content' block inside a 'Column' block, as long as 'Column' is somewhere within a 'Comment Template' block. In comparison to the `parent` property blocks that specify their `ancestor` can be placed anywhere in the subtree whilst blocks with a specified `parent` need to be direct children. - ```js // Only allow this block when it is nested at any level in a Columns block. ancestor: [ 'core/columns' ], diff --git a/docs/reference-guides/block-api/block-selectors.md b/docs/reference-guides/block-api/block-selectors.md new file mode 100644 index 00000000000000..1771e54c33708b --- /dev/null +++ b/docs/reference-guides/block-api/block-selectors.md @@ -0,0 +1,126 @@ +# Selectors + +
+ This API was stabilized in Gutenberg 15.5 and is planned for core release + in WordPress 6.3. To use this prior to WordPress 6.3, you will need to + install and activate Gutenberg >= 15.5. +
+ +Block Selectors is the API that allows blocks to customize the CSS selector used +when their styles are generated. + +A block may customize its CSS selectors at three levels: root, feature, and +subfeature. + +## Root Selector + +The root selector is the block's primary CSS selector. + +All blocks require a primary CSS selector for their style declarations to be +included under. If one is not provided through the Block Selectors API, a +default is generated in the form of `.wp-block-`. + +### Example + +```json +{ + ... + "selectors": { + "root": ".my-custom-block-selector" + } +} +``` + +## Feature Selectors + +Feature selectors relate to styles for a block support, e.g. border, color, +typography, etc. + +A block may wish to apply the styles for specific features to different +elements within a block. An example might be using colors on the block's wrapper +but applying the typography styles to an inner heading only. + +### Example + +```json +{ + ... + "selectors": { + "root": ".my-custom-block-selector", + "color": ".my-custom-block-selector", + "typography": ".my-custom-block-selector > h2" + } +} +``` + +## Subfeature Selectors + +These selectors relate to individual styles provided by a block support e.g. +`background-color` + +A subfeature can have styles generated under its own unique selector. This is +especially useful where one block support subfeature can't be applied to the +same element as the support's other subfeatures. + +A great example of this is `text-decoration`. Web browsers render this style +differently, making it difficult to override if added to a wrapper element. By +assigning `text-decoration` a custom selector, its style can target only the +elements to which it should be applied. + +### Example + +```json +{ + ... + "selectors": { + "root": ".my-custom-block-selector", + "color": ".my-custom-block-selector", + "typography": { + "root": ".my-custom-block-selector > h2", + "text-decoration": ".my-custom-block-selector > h2 span" + } + } +} +``` + +## Shorthand + +Rather than specify a CSS selector for every subfeature, you can set a single +selector as a string value for the relevant feature. This is the approach +demonstrated for the `color` feature in the earlier examples above. + +## Fallbacks + +A selector that hasn't been configured for a specific feature will fall back to +the block's root selector. Similarly, if a subfeature hasn't had a custom +selector set, it will fall back to its parent feature's selector and, if unavailable, fall back further to the block's root selector. + +Rather than repeating selectors for multiple subfeatures, you can set the +common selector as the parent feature's `root` selector and only define the +unique selectors for the subfeatures that differ. + +### Example + +```json +{ + ... + "selectors": { + "root": ".my-custom-block-selector", + "color": { + "text": ".my-custom-block-selector p" + }, + "typography": { + "root": ".my-custom-block-selector > h2", + "text-decoration": ".my-custom-block-selector > h2 span" + } + } +} +``` + +The `color.background-color` subfeature isn't explicitly set in the above +example. As the `color` feature also doesn't define a `root` selector, +`color.background-color` would be included under the block's primary root +selector, `.my-custom-block-selector`. + +For a subfeature such as `typography.font-size`, it would fallback to its parent +feature's selector given that is present, i.e. `.my-custom-block-selector > h2`. diff --git a/docs/reference-guides/block-api/block-supports.md b/docs/reference-guides/block-api/block-supports.md index 2e5f509f6dc48e..683e4f134d9877 100644 --- a/docs/reference-guides/block-api/block-supports.md +++ b/docs/reference-guides/block-api/block-supports.md @@ -139,7 +139,6 @@ supports: { - Default value: null - Subproperties: - `background`: type `boolean`, default value `true` - - `__experimentalDuotone`: type `string`, default value undefined - `gradients`: type `boolean`, default value `false` - `link`: type `boolean`, default value `false` - `text`: type `boolean`, default value `true` @@ -231,46 +230,9 @@ When the block declares support for `color.background`, the attributes definitio ### color.__experimentalDuotone -This property adds UI controls which allow to apply a duotone filter to a block or part of a block. +_**Note:** Deprecated since WordPress 6.3._ -The parent selector is automatically added much like nesting in Sass/SCSS (however, the `&` selector is not supported). - -```js -supports: { - color: { - // Apply the filter to the same selector in both edit and save. - __experimentalDuotone: '> .duotone-img, > .duotone-video', - - // Default values must be disabled if you don't want to use them with duotone. - background: false, - text: false - } -} -``` - -Duotone presets are sourced from `color.duotone` in [theme.json](/docs/how-to-guides/themes/theme-json.md). - -When the block declares support for `color.__experimentalDuotone`, the attributes definition is extended to include the attribute `style`: - -- `style`: attribute of `object` type with no default assigned. - - The block can apply a default duotone color by specifying its own attribute with a default e.g.: - - ```js - attributes: { - style: { - type: 'object', - default: { - color: { - duotone: [ - '#FFF', - '#000' - ] - } - } - } - } - ``` +This property has been replaced by [`filter.duotone`](#filter-duotone). ### color.gradients @@ -499,6 +461,60 @@ attributes: { } ``` +## filter +- Type: `Object` +- Default value: null +- Subproperties: + - `duotone`: type `boolean`, default value `false` + +This value signals that a block supports some of the properties related to filters. When it does, the block editor will show UI controls for the user to set their values. + +### filter.duotone + +This property adds UI controls which allow the user to apply a duotone filter to +a block or part of a block. + +```js +supports: { + filter: { + // Enable duotone support + duotone: true + } +}, +selectors: { + filter: { + // Apply the filter to img elements inside the image block + duotone: '.wp-block-image img' + } +} +``` + +The filter can be applied to an element inside the block by setting the `selectors.filter.duotone` selector. + +Duotone presets are sourced from `color.duotone` in [theme.json](/docs/how-to-guides/themes/theme-json.md). + +When the block declares support for `filter.duotone`, the attributes definition is extended to include the attribute `style`: + +- `style`: attribute of `object` type with no default assigned. + + The block can apply a default duotone color by specifying its own attribute with a default e.g.: + + ```js + attributes: { + style: { + type: 'object', + default: { + color: { + duotone: [ + '#FFF', + '#000' + ] + } + } + } + } + ``` + ## html - Type: `boolean` diff --git a/docs/reference-guides/block-api/block-templates.md b/docs/reference-guides/block-api/block-templates.md index a16ad6ea2548d1..2d8a21d510691c 100644 --- a/docs/reference-guides/block-api/block-templates.md +++ b/docs/reference-guides/block-api/block-templates.md @@ -1,6 +1,6 @@ -# Block Templates +# Templates -A block template is defined as a list of block items. Such blocks can have predefined attributes, placeholder content, and be static or dynamic. Block templates allow specifying a default initial state for an editor session. +A block template is defined as a list of block items. Such blocks can have predefined attributes, placeholder content, and be static or dynamic. Block templates allow specifying a default initial state for an editor session. The scope of templates include: @@ -134,7 +134,9 @@ attributes: { } } ``` + _Options:_ + - `remove` — Locks the ability of a block from being removed. - `move` — Locks the ability of a block from being moved. diff --git a/docs/reference-guides/block-api/block-transforms.md b/docs/reference-guides/block-api/block-transforms.md index a7d26508eae4ab..b2970e3d14ff2b 100644 --- a/docs/reference-guides/block-api/block-transforms.md +++ b/docs/reference-guides/block-api/block-transforms.md @@ -223,18 +223,9 @@ transforms: {

Schemas and Content Models

-When pasting content it's possible to define -a [content model](https://html.spec.whatwg.org/multipage/dom.html#content-models) that will be used to validate and -process pasted content. It's often the case that HTML pasted into the editor will contain a mixture of elements that _ -should_ transfer as well as elements that _shouldn't_. For example, consider -pasting `12:04 pm` into the editor. We want to copy `12:04 pm` and omit the `` and -its `class` attribute because those won't carry the same meaning or structure as they originally did from where they -were copied. - -When writing `raw` transforms you can control this by supplying a `schema` which describes allowable content and which -will be applied to clean up the pasted content before attempting to match with your block. The schemas are passed -into [`cleanNodeList` from `@wordpress/dom`](https://github.com/wordpress/gutenberg/blob/trunk/packages/dom/src/dom/clean-node-list.js); check there for -a [complete description of the schema](https://github.com/wordpress/gutenberg/blob/trunk/packages/dom/src/phrasing-content.js). +When pasting content it's possible to define a [content model](https://html.spec.whatwg.org/multipage/dom.html#content-models) that will be used to validate and process pasted content. It's often the case that HTML pasted into the editor will contain a mixture of elements that _should_ transfer as well as elements that _shouldn't_. For example, consider pasting `12:04 pm` into the editor. We want to copy `12:04 pm` and omit the `` and its `class` attribute because those won't carry the same meaning or structure as they originally did from where they were copied. + +When writing `raw` transforms you can control this by supplying a `schema` which describes allowable content and which will be applied to clean up the pasted content before attempting to match with your block. The schemas are passed into [`cleanNodeList` from `@wordpress/dom`](https://github.com/wordpress/gutenberg/blob/trunk/packages/dom/src/dom/clean-node-list.js); check there for a [complete description of the schema](https://github.com/wordpress/gutenberg/blob/trunk/packages/dom/src/phrasing-content.js). ```js schema = { span: { children: { '#text': {} } } } @@ -251,12 +242,8 @@ Suppose we want to match the following HTML snippet and turn it into some kind o ``` -We want to tell the editor to allow the inner `h2` and `p` elements. We do this by supplying the following schema. In -this example we're using the function form, which accepts an argument supplying `phrasingContentSchema` (as well as a -boolean `isPaste` indicating if the transformation operation started with pasting text). The `phrasingContentSchema` is -pre-defined to match HTML phrasing elements, such as `` and `` and ``. Anywhere we expect -a `` component is a good place to allow phrasing content otherwise we'll lose all text formatting on -conversion. +We want to tell the editor to allow the inner `h2` and `p` elements. We do this by supplying the following schema. In this example we're using the function form, which accepts an argument supplying `phrasingContentSchema` (as well as a boolean `isPaste` indicating if the transformation operation started with pasting text). The `phrasingContentSchema` is pre-defined to match HTML phrasing elements, such as `` and `` and ``. Anywhere we expect +a `` component is a good place to allow phrasing content otherwise we'll lose all text formatting on conversion. ```js schema = ({ phrasingContentSchema }) => { @@ -271,13 +258,9 @@ schema = ({ phrasingContentSchema }) => { } ``` -When we successfully match this content every HTML attribute will be stripped away except for `data-post-id` and if we -have other arrangements of HTML inside of a given `div` then it won't match our transformer. Likewise we'd fail to match -if we found an `

` in there instead of an `

`. +When we successfully match this content every HTML attribute will be stripped away except for `data-post-id` and if we have other arrangements of HTML inside of a given `div` then it won't match our transformer. Likewise we'd fail to match if we found an `

` in there instead of an `

`. -Schemas are most-important when wanting to match HTML snippets containing non-phrasing content, such as `
` with -a ``. Without declaring the custom schema the editor will skip over these other contructions before attempting -to run them through any block transforms. +Schemas are most-important when wanting to match HTML snippets containing non-phrasing content, such as `
` with a ``. Without declaring the custom schema the editor will skip over these other contructions before attempting to run them through any block transforms. ### Shortcode diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index d9f017b60a99e7..6f7314d5e51880 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -59,7 +59,7 @@ Prompt visitors to take action with a group of button-style links. ([Source](htt - **Name:** core/buttons - **Category:** design -- **Supports:** align (full, wide), anchor, spacing (blockGap, margin), typography (fontSize, lineHeight) +- **Supports:** align (full, wide), anchor, spacing (blockGap, margin), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** ## Calendar @@ -86,7 +86,7 @@ Display code snippets that respect your spacing and tabs. ([Source](https://gith - **Name:** core/code - **Category:** text -- **Supports:** anchor, color (background, gradients, text), spacing (margin, padding), typography (fontSize, lineHeight) +- **Supports:** align (wide), anchor, color (background, gradients, text), spacing (margin, padding), typography (fontSize, lineHeight) - **Attributes:** content ## Column @@ -105,7 +105,7 @@ Display content in multiple columns, with blocks added to each column. ([Source] - **Name:** core/columns - **Category:** design - **Supports:** align (full, wide), anchor, color (background, gradients, link, text), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ -- **Attributes:** isStackedOnMobile, verticalAlignment +- **Attributes:** isStackedOnMobile, templateLock, verticalAlignment ## Comment Author Avatar (deprecated) @@ -226,20 +226,29 @@ Displays a title with the number of comments ([Source](https://github.com/WordPr ## Cover -Add an image or video with a text overlay — great for headers. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/cover)) +Add an image or video with a text overlay. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/cover)) - **Name:** core/cover - **Category:** media -- **Supports:** align, anchor, color (~~background~~, ~~text~~), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** align, anchor, color (text, ~~background~~), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** allowedBlocks, alt, backgroundType, contentPosition, customGradient, customOverlayColor, dimRatio, focalPoint, gradient, hasParallax, id, isDark, isRepeated, minHeight, minHeightUnit, overlayColor, tagName, templateLock, url, useFeaturedImage +## Details + +Hide and show additional content. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/details)) + +- **Name:** core/details +- **Category:** text +- **Supports:** align (full, wide), color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ +- **Attributes:** showContent, summary + ## Embed Add a block that displays content pulled from other sites, like Twitter or YouTube. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/embed)) - **Name:** core/embed - **Category:** embed -- **Supports:** align +- **Supports:** align, spacing (margin) - **Attributes:** allowResponsive, caption, previewable, providerNameSlug, responsive, type, url ## File @@ -276,7 +285,7 @@ Gather blocks in a layout container. ([Source](https://github.com/WordPress/gute - **Name:** core/group - **Category:** design - **Supports:** align (full, wide), anchor, ariaLabel, color (background, gradients, link, text), dimensions (minHeight), position (sticky), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ -- **Attributes:** tagName, templateLock +- **Attributes:** allowedBlocks, tagName, templateLock ## Heading @@ -311,7 +320,7 @@ Insert an image to make a visual statement. ([Source](https://github.com/WordPre - **Name:** core/image - **Category:** media -- **Supports:** anchor, color (~~background~~, ~~text~~) +- **Supports:** anchor, color (~~background~~, ~~text~~), filter (duotone) - **Attributes:** align, alt, caption, height, href, id, linkClass, linkDestination, linkTarget, rel, sizeSlug, title, url, width ## Latest Comments @@ -356,7 +365,7 @@ Show login & logout links. ([Source](https://github.com/WordPress/gutenberg/tree - **Name:** core/loginout - **Category:** theme -- **Supports:** anchor, className, typography (~~fontSize~~) +- **Supports:** anchor, className, typography (fontSize, lineHeight) - **Attributes:** displayLoginAsForm, redirectToCurrent ## Media & Text @@ -366,7 +375,7 @@ Set media and words side-by-side for a richer layout. ([Source](https://github.c - **Name:** core/media-text - **Category:** media - **Supports:** align (full, wide), anchor, color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ -- **Attributes:** align, focalPoint, href, imageFill, isStackedOnMobile, linkClass, linkDestination, linkTarget, mediaAlt, mediaId, mediaLink, mediaPosition, mediaSizeSlug, mediaType, mediaUrl, mediaWidth, rel, verticalAlignment +- **Attributes:** align, allowedBlocks, focalPoint, href, imageFill, isStackedOnMobile, linkClass, linkDestination, linkTarget, mediaAlt, mediaId, mediaLink, mediaPosition, mediaSizeSlug, mediaType, mediaUrl, mediaWidth, rel, verticalAlignment ## Unsupported @@ -456,7 +465,7 @@ Show a block pattern. ([Source](https://github.com/WordPress/gutenberg/tree/trun - **Name:** core/pattern - **Category:** theme - **Supports:** ~~html~~, ~~inserter~~ -- **Attributes:** slug +- **Attributes:** slug, syncStatus ## Post Author @@ -572,7 +581,7 @@ Contains the block elements used to render a post, like the title, date, feature - **Name:** core/post-template - **Category:** theme -- **Supports:** align, anchor, color (background, gradients, link, text), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ +- **Supports:** align (full, wide), anchor, color (background, gradients, link, text), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ - **Attributes:** ## Post Terms @@ -590,7 +599,7 @@ Show minutes required to finish reading the post. ([Source](https://github.com/W - **Name:** core/post-time-to-read - **Category:** theme -- **Supports:** ~~html~~, ~~multiple~~ +- **Supports:** color (background, gradients, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** textAlign ## Post Title @@ -689,7 +698,7 @@ Give quoted text visual emphasis. "In quoting others, we cite ourselves." — Ju - **Name:** core/quote - **Category:** text -- **Supports:** anchor, color (background, gradients, link, text), typography (fontSize, lineHeight) +- **Supports:** anchor, color (background, gradients, link, text), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** align, citation, value ## Read More @@ -739,7 +748,7 @@ Insert additional custom elements with a WordPress shortcode. ([Source](https:// ## Site Logo -Display a graphic to represent this site. Update the block, and the changes apply everywhere it’s used. This is different than the site icon, which is the smaller image visible in your dashboard, browser tabs, etc used to help others recognize this site. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/site-logo)) +Display an image to represent this site. Update this block and the changes apply everywhere. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/site-logo)) - **Name:** core/site-logo - **Category:** theme diff --git a/docs/reference-guides/data/data-core-block-directory.md b/docs/reference-guides/data/data-core-block-directory.md index df12aec121da1b..c1fe96521adc32 100644 --- a/docs/reference-guides/data/data-core-block-directory.md +++ b/docs/reference-guides/data/data-core-block-directory.md @@ -46,8 +46,7 @@ _Returns_ ### getInstalledBlockTypes -Returns the block types that have been installed on the server in this -session. +Returns the block types that have been installed on the server in this session. _Parameters_ @@ -59,8 +58,7 @@ _Returns_ ### getNewBlockTypes -Returns block types that have been installed on the server and used in the -current post. +Returns block types that have been installed on the server and used in the current post. _Parameters_ @@ -72,8 +70,7 @@ _Returns_ ### getUnusedBlockTypes -Returns the block types that have been installed on the server but are not -used in the current post. +Returns the block types that have been installed on the server but are not used in the current post. _Parameters_ @@ -117,8 +114,7 @@ _Returns_ ### addInstalledBlockType -Returns an action object used to add a block type to the "newly installed" -tracking list. +Returns an action object used to add a block type to the "newly installed" tracking list. _Parameters_ @@ -142,8 +138,7 @@ _Returns_ ### fetchDownloadableBlocks -Returns an action object used in signalling that the downloadable blocks -have been requested and are loading. +Returns an action object used in signalling that the downloadable blocks have been requested and are loading. _Parameters_ @@ -167,8 +162,7 @@ _Returns_ ### receiveDownloadableBlocks -Returns an action object used in signalling that the downloadable blocks -have been updated. +Returns an action object used in signalling that the downloadable blocks have been updated. _Parameters_ @@ -181,8 +175,7 @@ _Returns_ ### removeInstalledBlockType -Returns an action object used to remove a block type from the "newly installed" -tracking list. +Returns an action object used to remove a block type from the "newly installed" tracking list. _Parameters_ diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md index b0f60d9621d580..a3d6fa25e97c89 100644 --- a/docs/reference-guides/data/data-core-block-editor.md +++ b/docs/reference-guides/data/data-core-block-editor.md @@ -34,8 +34,7 @@ _Returns_ ### canInsertBlocks -Determines if the given blocks are allowed to be inserted into the block -list. +Determines if the given blocks are allowed to be inserted into the block list. _Parameters_ @@ -144,10 +143,7 @@ _Returns_ ### getAdjacentBlockClientId -Returns the client ID of the block adjacent one at the given reference -startClientId and modifier directionality. Defaults start startClientId to -the selected block, and direction as next block. Returns null if there is no -adjacent block. +Returns the client ID of the block adjacent one at the given reference startClientId and modifier directionality. Defaults start startClientId to the selected block, and direction as next block. Returns null if there is no adjacent block. _Parameters_ @@ -174,20 +170,9 @@ _Returns_ ### getBlock -Returns a block given its client ID. This is a parsed copy of the block, -containing its `blockName`, `clientId`, and current `attributes` state. This -is not the block's registration settings, which must be retrieved from the -blocks module registration store. +Returns a block given its client ID. This is a parsed copy of the block, containing its `blockName`, `clientId`, and current `attributes` state. This is not the block's registration settings, which must be retrieved from the blocks module registration store. -getBlock recurses through its inner blocks until all its children blocks have -been retrieved. Note that getBlock will not return the child inner blocks of -an inner block controller. This is because an inner block controller syncs -itself with its own entity, and should therefore not be included with the -blocks of a different entity. For example, say you call `getBlocks( TP )` to -get the blocks of a template part. If another template part is a child of TP, -then the nested template part's child blocks will not be returned. This way, -the template block itself is considered part of the parent, but the children -are not. +getBlock recurses through its inner blocks until all its children blocks have been retrieved. Note that getBlock will not return the child inner blocks of an inner block controller. This is because an inner block controller syncs itself with its own entity, and should therefore not be included with the blocks of a different entity. For example, say you call `getBlocks( TP )` to get the blocks of a template part. If another template part is a child of TP, then the nested template part's child blocks will not be returned. This way, the template block itself is considered part of the parent, but the children are not. _Parameters_ @@ -200,8 +185,7 @@ _Returns_ ### getBlockAttributes -Returns a block's attributes given its client ID, or null if no block exists with -the client ID. +Returns a block's attributes given its client ID, or null if no block exists with the client ID. _Parameters_ @@ -240,8 +224,7 @@ _Returns_ ### getBlockIndex -Returns the index at which the block corresponding to the specified client -ID occurs within the block order, or `-1` if the block does not exist. +Returns the index at which the block corresponding to the specified client ID occurs within the block order, or `-1` if the block does not exist. _Parameters_ @@ -254,8 +237,7 @@ _Returns_ ### getBlockInsertionPoint -Returns the insertion point, the index at which the new inserted block would -be placed. Defaults to the last index. +Returns the insertion point, the index at which the new inserted block would be placed. Defaults to the last index. _Parameters_ @@ -280,8 +262,7 @@ _Returns_ ### getBlockMode -Returns the block's editing mode, defaulting to "visual" if not explicitly -assigned. +Returns the block's editing mode, defaulting to "visual" if not explicitly assigned. _Parameters_ @@ -294,8 +275,7 @@ _Returns_ ### getBlockName -Returns a block's name given its client ID, or null if no block exists with -the client ID. +Returns a block's name given its client ID, or null if no block exists with the client ID. _Parameters_ @@ -308,8 +288,7 @@ _Returns_ ### getBlockNamesByClientId -Given an array of block client IDs, returns the corresponding array of block -names. +Given an array of block client IDs, returns the corresponding array of block names. _Parameters_ @@ -322,9 +301,7 @@ _Returns_ ### getBlockOrder -Returns an array containing all block client IDs in the editor in the order -they appear. Optionally accepts a root client ID of the block list for which -the order should be returned, defaulting to the top-level block order. +Returns an array containing all block client IDs in the editor in the order they appear. Optionally accepts a root client ID of the block list for which the order should be returned, defaulting to the top-level block order. _Parameters_ @@ -351,11 +328,7 @@ _Returns_ ### getBlockParentsByBlockName -Given a block client ID and a block name, returns the list of all its parents -from top to bottom, filtered by the given name(s). For example, if passed -'core/group' as the blockName, it will only return parents which are group -blocks. If passed `[ 'core/group', 'core/cover']`, as the blockName, it will -return parents which are group blocks and parents which are cover blocks. +Given a block client ID and a block name, returns the list of all its parents from top to bottom, filtered by the given name(s). For example, if passed 'core/group' as the blockName, it will only return parents which are group blocks. If passed `[ 'core/group', 'core/cover']`, as the blockName, it will return parents which are group blocks and parents which are cover blocks. _Parameters_ @@ -370,9 +343,7 @@ _Returns_ ### getBlockRootClientId -Given a block client ID, returns the root block from which the block is -nested, an empty string for top-level blocks, or null if the block does not -exist. +Given a block client ID, returns the root block from which the block is nested, an empty string for top-level blocks, or null if the block does not exist. _Parameters_ @@ -385,9 +356,7 @@ _Returns_ ### getBlocks -Returns all block objects for the current post being edited as an array in -the order they appear in the post. Note that this will exclude child blocks -of nested inner block controllers. +Returns all block objects for the current post being edited as an array in the order they appear in the post. Note that this will exclude child blocks of nested inner block controllers. _Parameters_ @@ -400,8 +369,7 @@ _Returns_ ### getBlocksByClientId -Given an array of block client IDs, returns the corresponding array of block -objects. +Given an array of block client IDs, returns the corresponding array of block objects. _Parameters_ @@ -414,9 +382,7 @@ _Returns_ ### getBlockSelectionEnd -Returns the current block selection end. This value may be null, and it -may represent either a singular block selection or multi-selection end. -A selection is singular if its start and end match. +Returns the current block selection end. This value may be null, and it may represent either a singular block selection or multi-selection end. A selection is singular if its start and end match. _Parameters_ @@ -428,9 +394,7 @@ _Returns_ ### getBlockSelectionStart -Returns the current block selection start. This value may be null, and it -may represent either a singular block selection or multi-selection start. -A selection is singular if its start and end match. +Returns the current block selection start. This value may be null, and it may represent either a singular block selection or multi-selection start. A selection is singular if its start and end match. _Parameters_ @@ -444,11 +408,9 @@ _Returns_ Determines the items that appear in the available block transforms list. -Each item object contains what's necessary to display a menu item in the -transform list and handle its selection. +Each item object contains what's necessary to display a menu item in the transform list and handle its selection. -The 'frecency' property is a heuristic () -that combines block usage frequenty and recency. +The 'frecency' property is a heuristic () that combines block usage frequenty and recency. Items are returned ordered descendingly by their 'frecency'. @@ -477,9 +439,7 @@ _Properties_ ### getClientIdsOfDescendants -Returns an array containing the clientIds of all descendants of the blocks -given. Returned ids are ordered first by the order of the ids given, then -by the order that they appear in the editor. +Returns an array containing the clientIds of all descendants of the blocks given. Returned ids are ordered first by the order of the ids given, then by the order that they appear in the editor. _Parameters_ @@ -492,9 +452,7 @@ _Returns_ ### getClientIdsWithDescendants -Returns an array containing the clientIds of the top-level blocks and -their descendants of any depth (for nested blocks). Ids are returned -in the same order that they appear in the editor. +Returns an array containing the clientIds of the top-level blocks and their descendants of any depth (for nested blocks). Ids are returned in the same order that they appear in the editor. _Parameters_ @@ -520,8 +478,7 @@ _Returns_ ### getFirstMultiSelectedBlockClientId -Returns the client ID of the first block in the multi-selection set, or null -if there is no multi-selection. +Returns the client ID of the first block in the multi-selection set, or null if there is no multi-selection. _Parameters_ @@ -533,8 +490,7 @@ _Returns_ ### getGlobalBlockCount -Returns the total number of blocks, or the total number of blocks with a specific name in a post. -The number returned includes nested blocks. +Returns the total number of blocks, or the total number of blocks with a specific name in a post. The number returned includes nested blocks. _Parameters_ @@ -547,14 +503,11 @@ _Returns_ ### getInserterItems -Determines the items that appear in the inserter. Includes both static -items (e.g. a regular block type) and dynamic items (e.g. a reusable block). +Determines the items that appear in the inserter. Includes both static items (e.g. a regular block type) and dynamic items (e.g. a reusable block). -Each item object contains what's necessary to display a button in the -inserter and handle its selection. +Each item object contains what's necessary to display a button in the inserter and handle its selection. -The 'frecency' property is a heuristic () -that combines block usage frequenty and recency. +The 'frecency' property is a heuristic () that combines block usage frequenty and recency. Items are returned ordered descendingly by their 'utility' and 'frecency'. @@ -585,8 +538,7 @@ _Properties_ ### getLastMultiSelectedBlockClientId -Returns the client ID of the last block in the multi-selection set, or null -if there is no multi-selection. +Returns the client ID of the last block in the multi-selection set, or null if there is no multi-selection. _Parameters_ @@ -611,8 +563,7 @@ _Returns_ ### getMultiSelectedBlockClientIds -Returns the current multi-selection set of block client IDs, or an empty -array if there is no multi-selection. +Returns the current multi-selection set of block client IDs, or an empty array if there is no multi-selection. _Parameters_ @@ -624,8 +575,7 @@ _Returns_ ### getMultiSelectedBlocks -Returns the current multi-selection set of blocks, or an empty array if -there is no multi-selection. +Returns the current multi-selection set of blocks, or an empty array if there is no multi-selection. _Parameters_ @@ -637,8 +587,7 @@ _Returns_ ### getMultiSelectedBlocksEndClientId -Returns the client ID of the block which ends the multi-selection set, or -null if there is no multi-selection. +Returns the client ID of the block which ends the multi-selection set, or null if there is no multi-selection. This is not necessarily the last client ID in the selection. @@ -656,8 +605,7 @@ _Returns_ ### getMultiSelectedBlocksStartClientId -Returns the client ID of the block which begins the multi-selection set, or -null if there is no multi-selection. +Returns the client ID of the block which begins the multi-selection set, or null if there is no multi-selection. This is not necessarily the first client ID in the selection. @@ -675,9 +623,7 @@ _Returns_ ### getNextBlockClientId -Returns the next block's client ID from the given reference start ID. -Defaults start to the selected block. Returns null if there is no next -block. +Returns the next block's client ID from the given reference start ID. Defaults start to the selected block. Returns null if there is no next block. _Parameters_ @@ -690,11 +636,7 @@ _Returns_ ### getPatternsByBlockTypes -Returns the list of patterns based on their declared `blockTypes` -and a block's name. -Patterns can use `blockTypes` to integrate in work flows like -suggesting appropriate patterns in a Placeholder state(during insertion) -or blocks transformations. +Returns the list of patterns based on their declared `blockTypes` and a block's name. Patterns can use `blockTypes` to integrate in work flows like suggesting appropriate patterns in a Placeholder state(during insertion) or blocks transformations. _Parameters_ @@ -708,9 +650,7 @@ _Returns_ ### getPreviousBlockClientId -Returns the previous block's client ID from the given reference start ID. -Defaults start to the selected block. Returns null if there is no previous -block. +Returns the previous block's client ID from the given reference start ID. Defaults start to the selected block. Returns null if there is no previous block. _Parameters_ @@ -735,8 +675,7 @@ _Returns_ ### getSelectedBlockClientId -Returns the currently selected block client ID, or null if there is no -selected block. +Returns the currently selected block client ID, or null if there is no selected block. _Parameters_ @@ -772,9 +711,7 @@ _Returns_ ### getSelectedBlocksInitialCaretPosition -Returns the initial caret position for the selected block. -This position is to used to position the caret properly when the selected block changes. -If the current block is not a RichText, having initial position set to 0 means "focus block" +Returns the initial caret position for the selected block. This position is to used to position the caret properly when the selected block changes. If the current block is not a RichText, having initial position set to 0 means "focus block" _Parameters_ @@ -786,8 +723,7 @@ _Returns_ ### getSelectionEnd -Returns the current selection end block client ID, attribute key and text -offset. +Returns the current selection end block client ID, attribute key and text offset. _Parameters_ @@ -799,8 +735,7 @@ _Returns_ ### getSelectionStart -Returns the current selection start block client ID, attribute key and text -offset. +Returns the current selection start block client ID, attribute key and text offset. _Parameters_ @@ -836,8 +771,7 @@ _Returns_ ### getTemplateLock -Returns the defined block template lock. Optionally accepts a root block -client ID as context, otherwise defaulting to the global context. +Returns the defined block template lock. Optionally accepts a root block client ID as context, otherwise defaulting to the global context. _Parameters_ @@ -860,6 +794,20 @@ _Returns_ - `string`: Client Id of moving block. +### hasDraggedInnerBlock + +Returns true if one of the block's inner blocks is dragged. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. +- _deep_ `boolean`: Perform a deep check. + +_Returns_ + +- `boolean`: Whether the block has an inner block dragged + ### hasInserterItems Determines whether there are items to show in the inserter. @@ -909,7 +857,7 @@ _Parameters_ _Returns_ -- `boolean`: Whether the block as an inner block selected +- `boolean`: Whether the block has an inner block selected ### isAncestorBeingDragged @@ -926,8 +874,7 @@ _Returns_ ### isAncestorMultiSelected -Returns true if an ancestor of the block is multi-selected, or false -otherwise. +Returns true if an ancestor of the block is multi-selected, or false otherwise. _Parameters_ @@ -942,9 +889,7 @@ _Returns_ Returns whether the block is being dragged. -Only returns true if the block is being directly dragged, -not if the block is a child of a parent being dragged. -See `isAncestorBeingDragged` for child blocks. +Only returns true if the block is being directly dragged, not if the block is a child of a parent being dragged. See `isAncestorBeingDragged` for child blocks. _Parameters_ @@ -982,8 +927,7 @@ _Returns_ ### isBlockMultiSelected -Returns true if the client ID occurs within the block multi-selection, or -false otherwise. +Returns true if the client ID occurs within the block multi-selection, or false otherwise. _Parameters_ @@ -996,8 +940,7 @@ _Returns_ ### isBlockSelected -Returns true if the block corresponding to the specified client ID is -currently selected and no multi-selection exists, or false otherwise. +Returns true if the block corresponding to the specified client ID is currently selected and no multi-selection exists, or false otherwise. _Parameters_ @@ -1036,10 +979,7 @@ _Returns_ ### isBlockWithinSelection -Returns true if the block corresponding to the specified client ID is -currently selected but isn't the last of the selected blocks. Here "last" -refers to the block sequence in the document, _not_ the sequence of -multi-selection, which is why `state.selectionEnd` isn't used. +Returns true if the block corresponding to the specified client ID is currently selected but isn't the last of the selected blocks. Here "last" refers to the block sequence in the document, _not_ the sequence of multi-selection, which is why `state.selectionEnd` isn't used. _Parameters_ @@ -1074,9 +1014,7 @@ _Returns_ ### isFirstMultiSelectedBlock -Returns true if a multi-selection exists, and the block corresponding to the -specified client ID is the first block of the multi-selection set, or false -otherwise. +Returns true if a multi-selection exists, and the block corresponding to the specified client ID is the first block of the multi-selection set, or false otherwise. _Parameters_ @@ -1089,9 +1027,7 @@ _Returns_ ### isLastBlockChangePersistent -Returns true if the most recent block change is be considered persistent, or -false otherwise. A persistent change is one committed by BlockEditorProvider -via its `onChange` callback, in addition to `onInput`. +Returns true if the most recent block change is be considered persistent, or false otherwise. A persistent change is one committed by BlockEditorProvider via its `onChange` callback, in addition to `onInput`. _Parameters_ @@ -1103,9 +1039,7 @@ _Returns_ ### isMultiSelecting -Whether in the process of multi-selecting or not. This flag is only true -while the multi-selection is being selected (by mouse move), and is false -once the multi-selection has been settled. +Whether in the process of multi-selecting or not. This flag is only true while the multi-selection is being selected (by mouse move), and is false once the multi-selection has been settled. _Related_ @@ -1256,6 +1190,8 @@ _Parameters_ Action that inserts a single block, optionally at a specific index respective a root block list. +Only allowed blocks are inserted. The action may fail silently for blocks that are not allowed or if a templateLock is active on the block list. + _Parameters_ - _block_ `Object`: Block object to insert. @@ -1272,6 +1208,8 @@ _Returns_ Action that inserts an array of blocks, optionally at a specific index respective a root block list. +Only allowed blocks are inserted. The action may fail silently for blocks that are not allowed or if a templateLock is active on the block list. + _Parameters_ - _blocks_ `Object[]`: Block objects to insert. @@ -1348,9 +1286,7 @@ _Parameters_ > **Deprecated** -Returns an action object used in signalling that blocks have been received. -Unlike resetBlocks, these should be appended to the existing known set, not -replacing. +Returns an action object used in signalling that blocks have been received. Unlike resetBlocks, these should be appended to the existing known set, not replacing. _Parameters_ @@ -1362,8 +1298,7 @@ _Returns_ ### removeBlock -Returns an action object used in signalling that the block with the -specified client ID is to be removed. +Returns an action object used in signalling that the block with the specified client ID is to be removed. _Parameters_ @@ -1376,8 +1311,7 @@ _Returns_ ### removeBlocks -Yields action objects used in signalling that the blocks corresponding to -the set of specified client IDs are to be removed. +Yields action objects used in signalling that the blocks corresponding to the set of specified client IDs are to be removed. _Parameters_ @@ -1415,8 +1349,7 @@ _Returns_ ### replaceInnerBlocks -Returns an action object used in signalling that the inner blocks with the -specified client ID should be replaced. +Returns an action object used in signalling that the inner blocks with the specified client ID should be replaced. _Parameters_ @@ -1431,8 +1364,7 @@ _Returns_ ### resetBlocks -Action that resets blocks state to the specified array of blocks, taking precedence -over any other content reflected as an edit in state. +Action that resets blocks state to the specified array of blocks, taking precedence over any other content reflected as an edit in state. _Parameters_ @@ -1440,8 +1372,7 @@ _Parameters_ ### resetSelection -Returns an action object used in signalling that selection state should be -reset to the specified selection. +Returns an action object used in signalling that selection state should be reset to the specified selection. _Parameters_ @@ -1455,10 +1386,7 @@ _Returns_ ### selectBlock -Returns an action object used in signalling that the block with the -specified client ID has been selected, optionally accepting a position -value reflecting its selection directionality. An initialPosition of -1 -reflects a reverse selection. +Returns an action object used in signalling that the block with the specified client ID has been selected, optionally accepting a position value reflecting its selection directionality. An initialPosition of -1 reflects a reverse selection. _Parameters_ @@ -1486,8 +1414,7 @@ _Returns_ ### selectNextBlock -Yields action objects used in signalling that the block following the given -clientId should be selected. +Yields action objects used in signalling that the block following the given clientId should be selected. _Parameters_ @@ -1495,9 +1422,7 @@ _Parameters_ ### selectPreviousBlock -Yields action objects used in signalling that the block preceding the given -clientId (or optionally, its first parent from bottom to top) -should be selected. +Yields action objects used in signalling that the block preceding the given clientId (or optionally, its first parent from bottom to top) should be selected. _Parameters_ @@ -1639,8 +1564,7 @@ _Parameters_ ### toggleBlockMode -Returns an action object used to toggle the block editing mode between -visual and HTML modes. +Returns an action object used to toggle the block editing mode between visual and HTML modes. _Parameters_ @@ -1716,10 +1640,7 @@ _Returns_ ### validateBlocksToTemplate -Block validity is a function of blocks state (at the point of a -reset) and the template setting. As a compromise to its placement -across distinct parts of state, it is implemented here as a side- -effect of the block reset action. +Block validity is a function of blocks state (at the point of a reset) and the template setting. As a compromise to its placement across distinct parts of state, it is implemented here as a side effect of the block reset action. _Parameters_ diff --git a/docs/reference-guides/data/data-core-blocks.md b/docs/reference-guides/data/data-core-blocks.md index 8bf4d19d7134bf..ba047160f047de 100644 --- a/docs/reference-guides/data/data-core-blocks.md +++ b/docs/reference-guides/data/data-core-blocks.md @@ -8,16 +8,11 @@ Namespace: `core/blocks`. ### getActiveBlockVariation -Returns the active block variation for a given block based on its attributes. -Variations are determined by their `isActive` property. -Which is either an array of block attribute keys or a function. +Returns the active block variation for a given block based on its attributes. Variations are determined by their `isActive` property. Which is either an array of block attribute keys or a function. -In case of an array of block attribute keys, the `attributes` are compared -to the variation's attributes using strict equality check. +In case of an array of block attribute keys, the `attributes` are compared to the variation's attributes using strict equality check. -In case of function type, the function should accept a block's attributes -and the variation's attributes and determines if a variation is active. -A function that accepts a block's attributes and the variation's attributes and determines if a variation is active. +In case of function type, the function should accept a block's attributes and the variation's attributes and determines if a variation is active. A function that accepts a block's attributes and the variation's attributes and determines if a variation is active. _Usage_ @@ -394,10 +389,7 @@ _Returns_ ### getDefaultBlockVariation -Returns the default block variation for the given block type. -When there are multiple variations annotated as the default one, -the last added item is picked. This simplifies registering overrides. -When there is no default variation set, it returns the first item. +Returns the default block variation for the given block type. When there are multiple variations annotated as the default one, the last added item is picked. This simplifies registering overrides. When there is no default variation set, it returns the first item. _Usage_ @@ -670,8 +662,7 @@ _Returns_ ### isMatchingSearchTerm -Returns true if the block type by the given name or object value matches a -search term, or false otherwise. +Returns true if the block type by the given name or object value matches a search term, or false otherwise. _Usage_ diff --git a/docs/reference-guides/data/data-core-edit-post.md b/docs/reference-guides/data/data-core-edit-post.md index 1c082528485c35..aee88ce40bc731 100644 --- a/docs/reference-guides/data/data-core-edit-post.md +++ b/docs/reference-guides/data/data-core-edit-post.md @@ -20,9 +20,7 @@ _Returns_ ### getActiveGeneralSidebarName -Returns the current active general sidebar name, or null if there is no -general sidebar active. The active general sidebar is a unique name to -identify either an editor or plugin sidebar. +Returns the current active general sidebar name, or null if there is no general sidebar active. The active general sidebar is a unique name to identify either an editor or plugin sidebar. Examples: @@ -152,8 +150,7 @@ _Returns_ ### isEditorPanelEnabled -Returns true if the given panel is enabled, or false otherwise. Panels are -enabled by default. +Returns true if the given panel is enabled, or false otherwise. Panels are enabled by default. _Parameters_ @@ -166,8 +163,7 @@ _Returns_ ### isEditorPanelOpened -Returns true if the given panel is open, or false otherwise. Panels are -closed by default. +Returns true if the given panel is open, or false otherwise. Panels are closed by default. _Parameters_ @@ -180,8 +176,7 @@ _Returns_ ### isEditorPanelRemoved -Returns true if the given panel was programmatically removed, or false otherwise. -All panels are not removed by default. +Returns true if the given panel was programmatically removed, or false otherwise. All panels are not removed by default. _Parameters_ @@ -243,8 +238,7 @@ _Returns_ ### isMetaBoxLocationActive -Returns true if there is an active meta box in the given location, or false -otherwise. +Returns true if there is an active meta box in the given location, or false otherwise. _Parameters_ @@ -283,8 +277,7 @@ _Returns_ ### isPluginItemPinned -Returns true if the plugin item is pinned to the header. -When the value is not set it defaults to true. +Returns true if the plugin item is pinned to the header. When the value is not set it defaults to true. _Parameters_ @@ -351,8 +344,7 @@ _Returns_ ### closePublishSidebar -Returns an action object used in signalling that the user closed the -publish sidebar. +Returns an action object used in signalling that the user closed the publish sidebar. _Returns_ @@ -408,8 +400,7 @@ _Returns_ ### openPublishSidebar -Returns an action object used in signalling that the user opened the publish -sidebar. +Returns an action object used in signalling that the user opened the publish sidebar. _Returns_ diff --git a/docs/reference-guides/data/data-core-edit-site.md b/docs/reference-guides/data/data-core-edit-site.md index 53fd4d138383ae..0dad03bbc8ca2a 100644 --- a/docs/reference-guides/data/data-core-edit-site.md +++ b/docs/reference-guides/data/data-core-edit-site.md @@ -133,6 +133,8 @@ _Returns_ ### isFeatureActive +> **Deprecated** + Returns whether the given feature is enabled or not. _Parameters_ @@ -302,8 +304,7 @@ _Returns_ ### setPage -Resolves the template for a page and displays both. If no path is given, attempts -to use the postId to generate a path like `?p=${ postId }`. +Resolves the template for a page and displays both. If no path is given, attempts to use the postId to generate a path like `?p=${ postId }`. _Parameters_ diff --git a/docs/reference-guides/data/data-core-edit-widgets.md b/docs/reference-guides/data/data-core-edit-widgets.md index 4b2e2dbedd93c0..2dde89bf69ad73 100644 --- a/docs/reference-guides/data/data-core-edit-widgets.md +++ b/docs/reference-guides/data/data-core-edit-widgets.md @@ -161,8 +161,7 @@ _Parameters_ ### persistStubPost -Persists a stub post with given ID to core data store. The post is meant to be in-memory only and -shouldn't be saved via the API. +Persists a stub post with given ID to core data store. The post is meant to be in-memory only and shouldn't be saved via the API. _Parameters_ @@ -175,8 +174,7 @@ _Returns_ ### saveEditedWidgetAreas -Converts all the blocks from edited widget areas into widgets, -and submits a batch request to save everything at once. +Converts all the blocks from edited widget areas into widgets, and submits a batch request to save everything at once. Creates a snackbar notice on either success or error. @@ -186,8 +184,7 @@ _Returns_ ### saveWidgetArea -Converts all the blocks from a widget area specified by ID into widgets, -and submits a batch request to save everything at once. +Converts all the blocks from a widget area specified by ID into widgets, and submits a batch request to save everything at once. _Parameters_ @@ -199,8 +196,7 @@ _Returns_ ### saveWidgetAreas -Converts all the blocks from specified widget areas into widgets, -and submits a batch request to save everything at once. +Converts all the blocks from specified widget areas into widgets, and submits a batch request to save everything at once. _Parameters_ diff --git a/docs/reference-guides/data/data-core-editor.md b/docs/reference-guides/data/data-core-editor.md index 2d924178a04225..5dbcb095bbf085 100644 --- a/docs/reference-guides/data/data-core-editor.md +++ b/docs/reference-guides/data/data-core-editor.md @@ -26,8 +26,7 @@ _Returns_ ### didPostSaveRequestFail -Returns true if a previous post save was attempted but failed, or false -otherwise. +Returns true if a previous post save was attempted but failed, or false otherwise. _Parameters_ @@ -39,8 +38,7 @@ _Returns_ ### didPostSaveRequestSucceed -Returns true if a previous post save was attempted successfully, or false -otherwise. +Returns true if a previous post save was attempted successfully, or false otherwise. _Parameters_ @@ -72,8 +70,7 @@ _Related_ > **Deprecated** since 5.6. Callers should use the `getAutosave( postType, postId, userId )` selector from the '@wordpress/core-data' package and access properties on the returned autosave object using getPostRawValue. -Returns an attribute value of the current autosave revision for a post, or -null if there is no autosave for the post. +Returns an attribute value of the current autosave revision for a post, or null if there is no autosave for the post. _Parameters_ @@ -188,9 +185,7 @@ _Related_ ### getCurrentPost -Returns the post currently being edited in its last known saved state, not -including unsaved edits. Returns an object containing relevant default post -values if the post has not yet been saved. +Returns the post currently being edited in its last known saved state, not including unsaved edits. Returns an object containing relevant default post values if the post has not yet been saved. _Parameters_ @@ -215,8 +210,7 @@ _Returns_ ### getCurrentPostId -Returns the ID of the post currently being edited, or null if the post has -not yet been saved. +Returns the ID of the post currently being edited, or null if the post has not yet been saved. _Parameters_ @@ -228,8 +222,7 @@ _Returns_ ### getCurrentPostLastRevisionId -Returns the last revision ID of the post currently being edited, -or null if the post has no revisions. +Returns the last revision ID of the post currently being edited, or null if the post has no revisions. _Parameters_ @@ -265,9 +258,7 @@ _Returns_ ### getEditedPostAttribute -Returns a single attribute of the post being edited, preferring the unsaved -edit if one exists, but falling back to the attribute for the last known -saved state of the post. +Returns a single attribute of the post being edited, preferring the unsaved edit if one exists, but falling back to the attribute for the last known saved state of the post. _Parameters_ @@ -304,9 +295,7 @@ _Returns_ ### getEditedPostSlug -Returns the slug for the post being edited, preferring a manually edited -value if one exists, then a sanitized version of the current post title, and -finally the post ID. +Returns the slug for the post being edited, preferring a manually edited value if one exists, then a sanitized version of the current post title, and finally the post ID. _Parameters_ @@ -318,9 +307,7 @@ _Returns_ ### getEditedPostVisibility -Returns the current visibility of the post being edited, preferring the -unsaved value if different than the saved post. The return value is one of -"private", "password", or "public". +Returns the current visibility of the post being edited, preferring the unsaved value if different than the saved post. The return value is one of "private", "password", or "public". _Parameters_ @@ -462,8 +449,7 @@ _Returns_ ### getPermalinkParts -Returns the permalink for a post, split into it's three parts: the prefix, -the postName, and the suffix. +Returns the permalink for a post, split into it's three parts: the prefix, the postName, and the suffix. _Parameters_ @@ -475,8 +461,7 @@ _Returns_ ### getPostEdits -Returns any post values which have been changed in the editor but not yet -been saved. +Returns any post values which have been changed in the editor but not yet been saved. _Parameters_ @@ -544,14 +529,11 @@ _Related_ > **Deprecated** since Gutenberg 9.7.0. -Returns state object prior to a specified optimist transaction ID, or `null` -if the transaction corresponding to the given ID cannot be found. +Returns state object prior to a specified optimist transaction ID, or `null` if the transaction corresponding to the given ID cannot be found. ### getSuggestedPostFormat -Returns a suggested post format for the current post, inferred only if there -is a single block within the post and it is of a type known to match a -default post format. Returns null if the format cannot be determined. +Returns a suggested post format for the current post, inferred only if there is a single block within the post and it is of a type known to match a default post format. Returns null if the format cannot be determined. _Parameters_ @@ -587,8 +569,7 @@ _Returns_ ### hasEditorRedo -Returns true if any future editor history snapshots exist, or false -otherwise. +Returns true if any future editor history snapshots exist, or false otherwise. _Parameters_ @@ -624,8 +605,7 @@ _Related_ ### hasNonPostEntityChanges -Returns true if there are unsaved edits for entities other than -the editor's post, and false otherwise. +Returns true if there are unsaved edits for entities other than the editor's post, and false otherwise. _Parameters_ @@ -651,8 +631,7 @@ _Related_ > **Deprecated** since Gutenberg 9.7.0. -Returns true if an optimistic transaction is pending commit, for which the -before state satisfies the given predicate function. +Returns true if an optimistic transaction is pending commit, for which the before state satisfies the given predicate function. ### isAncestorMultiSelected @@ -710,8 +689,7 @@ _Related_ ### isCleanNewPost -Returns true if there are no unsaved values for the current edit session and -if the currently edited post is new (has never been saved before). +Returns true if there are no unsaved values for the current edit session and if the currently edited post is new (has never been saved before). _Parameters_ @@ -785,8 +763,7 @@ _Returns_ ### isEditedPostBeingScheduled -Return true if the post being edited is being scheduled. Preferring the -unsaved status values. +Return true if the post being edited is being scheduled. Preferring the unsaved status values. _Parameters_ @@ -798,13 +775,9 @@ _Returns_ ### isEditedPostDateFloating -Returns whether the current post should be considered to have a "floating" -date (i.e. that it would publish "Immediately" rather than at a set time). +Returns whether the current post should be considered to have a "floating" date (i.e. that it would publish "Immediately" rather than at a set time). -Unlike in the PHP backend, the REST API returns a full date string for posts -where the 0000-00-00T00:00:00 placeholder is present in the database. To -infer that a post is set to publish "Immediately" we check whether the date -and modified date are the same. +Unlike in the PHP backend, the REST API returns a full date string for posts where the 0000-00-00T00:00:00 placeholder is present in the database. To infer that a post is set to publish "Immediately" we check whether the date and modified date are the same. _Parameters_ @@ -816,8 +789,7 @@ _Returns_ ### isEditedPostDirty -Returns true if there are unsaved values for the current edit session, or -false if the editing state matches the saved or new post. +Returns true if there are unsaved values for the current edit session, or false if the editing state matches the saved or new post. _Parameters_ @@ -829,9 +801,7 @@ _Returns_ ### isEditedPostEmpty -Returns true if the edited post has content. A post has content if it has at -least one saveable block or otherwise has a non-empty content property -assigned. +Returns true if the edited post has content. A post has content if it has at least one saveable block or otherwise has a non-empty content property assigned. _Parameters_ @@ -843,8 +813,7 @@ _Returns_ ### isEditedPostNew -Returns true if the currently edited post is yet to be saved, or false if -the post has been saved. +Returns true if the currently edited post is yet to be saved, or false if the post has been saved. _Parameters_ @@ -868,8 +837,7 @@ _Returns_ ### isEditedPostSaveable -Returns true if the post can be saved, or false otherwise. A post must -contain a title, an excerpt, or non-empty content to be valid for save. +Returns true if the post can be saved, or false otherwise. A post must contain a title, an excerpt, or non-empty content to be valid for save. _Parameters_ @@ -977,8 +945,7 @@ _Returns_ ### isPublishSidebarEnabled -Returns whether the pre-publish panel should be shown -or skipped when the user clicks the "publish" button. +Returns whether the pre-publish panel should be shown or skipped when the user clicks the "publish" button. _Returns_ @@ -1034,10 +1001,7 @@ _Related_ ### autosave -Action that autosaves the current post. This -includes server-side autosaving (default) and client-side (a.k.a. local) -autosaving (e.g. on the Web, the post might be committed to Session -Storage). +Action that autosaves the current post. This includes server-side autosaving (default) and client-side (a.k.a. local) autosaving (e.g. on the Web, the post might be committed to Session Storage). _Parameters_ @@ -1061,8 +1025,7 @@ Disables the publish sidebar. ### editPost -Returns an action object used in signalling that attributes of the post have -been edited. +Returns an action object used in signalling that attributes of the post have been edited. _Parameters_ @@ -1262,8 +1225,7 @@ _Parameters_ > **Deprecated** Since WordPress 6.0. -Returns an action object used in signalling that the latest version of the -post has been received, either by initialization or save. +Returns an action object used in signalling that the latest version of the post has been received, either by initialization or save. ### savePost @@ -1287,8 +1249,7 @@ _Related_ ### setupEditor -Returns an action generator used in signalling that editor has initialized with -the specified post object and editor settings. +Returns an action generator used in signalling that editor has initialized with the specified post object and editor settings. _Parameters_ @@ -1298,8 +1259,7 @@ _Parameters_ ### setupEditorState -Returns an action object used to setup the editor state when first opening -an editor. +Returns an action object used to setup the editor state when first opening an editor. _Parameters_ @@ -1425,8 +1385,7 @@ Undocumented declaration. > **Deprecated** since Gutenberg 9.7.0. -Returns an action object used in signalling that a patch of updates for the -latest version of the post have been received. +Returns an action object used in signalling that a patch of updates for the latest version of the post have been received. _Returns_ diff --git a/docs/reference-guides/data/data-core-notices.md b/docs/reference-guides/data/data-core-notices.md index 271abf9cb74271..c5e3bc017a9146 100644 --- a/docs/reference-guides/data/data-core-notices.md +++ b/docs/reference-guides/data/data-core-notices.md @@ -8,8 +8,7 @@ Namespace: `core/notices`. ### getNotices -Returns all notices as an array, optionally for a given context. Defaults to -the global context. +Returns all notices as an array, optionally for a given context. Defaults to the global context. _Usage_ @@ -48,8 +47,7 @@ _Returns_ ### createErrorNotice -Returns an action object used in signalling that an error notice is to be -created. Refer to `createNotice` for options documentation. +Returns an action object used in signalling that an error notice is to be created. Refer to `createNotice` for options documentation. _Related_ @@ -93,8 +91,7 @@ _Returns_ ### createInfoNotice -Returns an action object used in signalling that an info notice is to be -created. Refer to `createNotice` for options documentation. +Returns an action object used in signalling that an info notice is to be created. Refer to `createNotice` for options documentation. _Related_ @@ -159,7 +156,7 @@ const ExampleComponent = () => { _Parameters_ -- _status_ `[string]`: Notice status. +- _status_ `string|undefined`: Notice status ("info" if undefined is passed). - _content_ `string`: Notice message. - _options_ `[Object]`: Notice options. - _options.context_ `[string]`: Context under which to group notice. @@ -178,8 +175,7 @@ _Returns_ ### createSuccessNotice -Returns an action object used in signalling that a success notice is to be -created. Refer to `createNotice` for options documentation. +Returns an action object used in signalling that a success notice is to be created. Refer to `createNotice` for options documentation. _Related_ @@ -221,8 +217,7 @@ _Returns_ ### createWarningNotice -Returns an action object used in signalling that a warning notice is to be -created. Refer to `createNotice` for options documentation. +Returns an action object used in signalling that a warning notice is to be created. Refer to `createNotice` for options documentation. _Related_ diff --git a/docs/reference-guides/data/data-core-preferences.md b/docs/reference-guides/data/data-core-preferences.md index 87c743fd666b59..a07aad43992bd1 100644 --- a/docs/reference-guides/data/data-core-preferences.md +++ b/docs/reference-guides/data/data-core-preferences.md @@ -8,8 +8,7 @@ Namespace: `core/preferences`. ### get -Returns a boolean indicating whether a prefer is active for a particular -scope. +Returns a boolean indicating whether a prefer is active for a particular scope. _Parameters_ @@ -29,8 +28,7 @@ _Returns_ ### set -Returns an action object used in signalling that a preference should be set -to a value +Returns an action object used in signalling that a preference should be set to a value _Parameters_ @@ -44,8 +42,7 @@ _Returns_ ### setDefaults -Returns an action object used in signalling that preference defaults should -be set. +Returns an action object used in signalling that preference defaults should be set. _Parameters_ @@ -65,9 +62,7 @@ When a persistence layer is set, the preferences store will: - call `get` immediately and update the store state to the value returned. - call `set` with all preferences whenever a preference changes value. -`setPersistenceLayer` should ideally be dispatched at the start of an -application's lifecycle, before any other actions have been dispatched to -the preferences store. +`setPersistenceLayer` should ideally be dispatched at the start of an application's lifecycle, before any other actions have been dispatched to the preferences store. _Parameters_ @@ -79,8 +74,7 @@ _Returns_ ### toggle -Returns an action object used in signalling that a preference should be -toggled. +Returns an action object used in signalling that a preference should be toggled. _Parameters_ diff --git a/docs/reference-guides/data/data-core-rich-text.md b/docs/reference-guides/data/data-core-rich-text.md index 2c8e528ed3e54b..da0e4d61197c0c 100644 --- a/docs/reference-guides/data/data-core-rich-text.md +++ b/docs/reference-guides/data/data-core-rich-text.md @@ -21,8 +21,7 @@ _Returns_ ### getFormatTypeForBareElement -Gets the format type, if any, that can handle a bare element (without a -data-format-type attribute), given the tag name of this element. +Gets the format type, if any, that can handle a bare element (without a data-format-type attribute), given the tag name of this element. _Parameters_ @@ -66,8 +65,7 @@ _Returns_ ### addFormatTypes -Returns an action object used in signalling that format types have been -added. +Returns an action object used in signalling that format types have been added. _Parameters_ diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index 79370974717671..1ee04e09550e2d 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -8,11 +8,9 @@ Namespace: `core`. ### canUser -Returns whether the current user can perform the given action on the given -REST resource. +Returns whether the current user can perform the given action on the given REST resource. -Calling this may trigger an OPTIONS request to the REST API via the -`canUser()` resolver. +Calling this may trigger an OPTIONS request to the REST API via the `canUser()` resolver. @@ -31,8 +29,7 @@ _Returns_ Returns whether the current user can edit the given entity. -Calling this may trigger an OPTIONS request to the REST API via the -`canUser()` resolver. +Calling this may trigger an OPTIONS request to the REST API via the `canUser()` resolver. @@ -56,7 +53,7 @@ Returns all available authors. _Parameters_ - _state_ `State`: Data state. -- _query_ `GetRecordsHttpQuery`: Optional object of query parameters to include with request. +- _query_ `GetRecordsHttpQuery`: Optional object of query parameters to include with request. For valid query parameters see the [Users page](https://developer.wordpress.org/rest-api/reference/users/) in the REST API Handbook and see the arguments for [List Users](https://developer.wordpress.org/rest-api/reference/users/#list-users) and [Retrieve a User](https://developer.wordpress.org/rest-api/reference/users/#retrieve-a-user). _Returns_ @@ -81,8 +78,7 @@ _Returns_ Returns the latest autosaves for the post. -May return multiple autosaves since the backend stores one autosave per -author for each post. +May return multiple autosaves since the backend stores one autosave per author for each post. _Parameters_ @@ -130,6 +126,18 @@ _Returns_ - `any`: The current theme. +### getCurrentThemeGlobalStylesRevisions + +Returns the revisions of the current global styles theme. + +_Parameters_ + +- _state_ `State`: Data state. + +_Returns_ + +- `Object | null`: The current global styles. + ### getCurrentUser Returns the current user. @@ -230,9 +238,7 @@ _Returns_ ### getEntityRecord -Returns the Entity's record object by key. Returns `null` if the value is not -yet received, undefined if the value entity is known to not exist, or the -entity object if it exists and is received. +Returns the Entity's record object by key. Returns `null` if the value is not yet received, undefined if the value entity is known to not exist, or the entity object if it exists and is received. _Parameters_ @@ -240,7 +246,7 @@ _Parameters_ - _kind_ `string`: Entity kind. - _name_ `string`: Entity name. - _key_ `EntityRecordKey`: Record's key -- _query_ `GetRecordsHttpQuery`: Optional query. If requesting specific fields, fields must always include the ID. +- _query_ `GetRecordsHttpQuery`: Optional query. If requesting specific fields, fields must always include the ID. For valid query parameters see the [Reference](https://developer.wordpress.org/rest-api/reference/) in the REST API Handbook and select the entity kind. Then see the arguments available "Retrieve a [Entity kind]". _Returns_ @@ -265,9 +271,7 @@ _Returns_ Returns the specified entity record's non transient edits. -Transient edits don't create an undo level, and -are not considered for change detection. -They are defined in the entity's config. +Transient edits don't create an undo level, and are not considered for change detection. They are defined in the entity's config. _Parameters_ @@ -289,7 +293,7 @@ _Parameters_ - _state_ `State`: State tree - _kind_ `string`: Entity kind. - _name_ `string`: Entity name. -- _query_ `GetRecordsHttpQuery`: Optional terms query. If requesting specific fields, fields must always include the ID. +- _query_ `GetRecordsHttpQuery`: Optional terms query. If requesting specific fields, fields must always include the ID. For valid query parameters see the [Reference](https://developer.wordpress.org/rest-api/reference/) in the REST API Handbook and select the entity kind. Then see the arguments available for "List [Entity kind]s". _Returns_ @@ -325,10 +329,21 @@ _Returns_ - `any`: The entity record's save error. +### getNavigationFallbackId + +Retrieve the fallback Navigation. + +_Parameters_ + +- _state_ `State`: Data state. + +_Returns_ + +- `EntityRecordKey | undefined`: The ID for the fallback Navigation post. + ### getRawEntityRecord -Returns the entity's record object by key, -with its attributes mapped to their raw values. +Returns the entity's record object by key, with its attributes mapped to their raw values. _Parameters_ @@ -343,8 +358,7 @@ _Returns_ ### getRedoEdit -Returns the next edit from the current undo offset -for the entity records edits history, if any. +Returns the next edit from the current undo offset for the entity records edits history, if any. _Parameters_ @@ -356,9 +370,7 @@ _Returns_ ### getReferenceByDistinctEdits -Returns a new reference when edited values have changed. This is useful in -inferring where an edit has been made between states by comparison of the -return values using strict equality. +Returns a new reference when edited values have changed. This is useful in inferring where an edit has been made between states by comparison of the return values using strict equality. _Usage_ @@ -389,8 +401,7 @@ _Returns_ ### getUndoEdit -Returns the previous edit from the current undo offset -for the entity records edits history, if any. +Returns the previous edit from the current undo offset for the entity records edits history, if any. _Parameters_ @@ -415,8 +426,7 @@ _Returns_ ### hasEditsForEntityRecord -Returns true if the specified entity record has edits, -and false otherwise. +Returns true if the specified entity record has edits, and false otherwise. _Parameters_ @@ -431,15 +441,14 @@ _Returns_ ### hasEntityRecords -Returns true if records have been received for the given set of parameters, -or false otherwise. +Returns true if records have been received for the given set of parameters, or false otherwise. _Parameters_ - _state_ `State`: State tree - _kind_ `string`: Entity kind. - _name_ `string`: Entity name. -- _query_ `GetRecordsHttpQuery`: Optional terms query. +- _query_ `GetRecordsHttpQuery`: Optional terms query. For valid query parameters see the [Reference](https://developer.wordpress.org/rest-api/reference/) in the REST API Handbook and select the entity kind. Then see the arguments available for "List [Entity kind]s". _Returns_ @@ -461,8 +470,7 @@ _Returns_ ### hasRedo -Returns true if there is a next edit from the current undo offset -for the entity records edits history, and false otherwise. +Returns true if there is a next edit from the current undo offset for the entity records edits history, and false otherwise. _Parameters_ @@ -474,8 +482,7 @@ _Returns_ ### hasUndo -Returns true if there is a previous edit from the current undo offset -for the entity records edits history, and false otherwise. +Returns true if there is a previous edit from the current undo offset for the entity records edits history, and false otherwise. _Parameters_ @@ -519,9 +526,7 @@ _Returns_ Determines if the returned preview is an oEmbed link fallback. -WordPress can be configured to return a simple link to a URL if it is not embeddable. -We need to be able to determine if a URL is embeddable or not, based on what we -get back from the oEmbed preview API. +WordPress can be configured to return a simple link to a URL if it is not embeddable. We need to be able to determine if a URL is embeddable or not, based on what we get back from the oEmbed preview API. _Parameters_ @@ -534,8 +539,7 @@ _Returns_ ### isRequestingEmbedPreview -Returns true if a request is in progress for embed preview data, or false -otherwise. +Returns true if a request is in progress for embed preview data, or false otherwise. _Parameters_ @@ -595,8 +599,7 @@ _Parameters_ ### editEntityRecord -Returns an action object that triggers an -edit to an entity record. +Returns an action object that triggers an edit to an entity record. _Parameters_ @@ -628,6 +631,18 @@ _Returns_ - `Object`: Action object. +### receiveNavigationFallbackId + +Returns an action object signalling that the fallback Navigation Menu id has been received. + +_Parameters_ + +- _fallbackId_ `integer`: the id of the fallback Navigation Menu + +_Returns_ + +- `Object`: Action object. + ### receiveThemeSupports > **Deprecated** since WP 5.9, this is not useful anymore, use the selector direclty. @@ -654,8 +669,7 @@ _Returns_ ### redo -Action triggered to redo the last undoed -edit to an entity record, if any. +Action triggered to redo the last undoed edit to an entity record, if any. ### saveEditedEntityRecord @@ -684,7 +698,6 @@ _Parameters_ ### undo -Action triggered to undo the last edit to -an entity record, if any. +Action triggered to undo the last edit to an entity record, if any. diff --git a/docs/reference-guides/slotfills/plugin-document-setting-panel.md b/docs/reference-guides/slotfills/plugin-document-setting-panel.md index fcec1d4344354c..76e077056abd63 100644 --- a/docs/reference-guides/slotfills/plugin-document-setting-panel.md +++ b/docs/reference-guides/slotfills/plugin-document-setting-panel.md @@ -33,15 +33,68 @@ registerPlugin( 'plugin-document-setting-panel-demo', { ## Accessing a panel programmatically +Core and custom panels can be access programmatically using their panel name. The core panel names are: + +- Summary Panel: `post-status` +- Categories Panel: `taxonomy-panel-category` +- Tags Panel: `taxonomy-panel-post_tag` +- Featured Image Panel: `featured-image` +- Excerpt Panel: `post-excerpt` +- DiscussionPanel: `discussion-panel` + Custom panels are namespaced with the plugin name that was passed to `registerPlugin`. -In order to access the panels using function such as `wp.data.dispatch( 'core/edit-post' ).toggleEditorPanelOpened` or `wp.data.dispatch( 'core/edit-post' ).toggleEditorPanelEnabled` be sure to prepend the namespace. +In order to access the panels using function such as `toggleEditorPanelOpened` or `toggleEditorPanelEnabled` be sure to prepend the namespace. -To programmatically toggle the custom panel added in the example above, use the following: +To programmatically toggle panels, use the following: ```js -wp.data - .dispatch( 'core/edit-post' ) - .toggleEditorPanelOpened( - 'plugin-document-setting-panel-demo/custom-panel' +import { useDispatch } from '@wordpress/data'; +import { store as editPostStore } from '@wordpress/edit-post'; + +const Example = () => { + const { toggleEditorPanelOpened } = useDispatch( editPostStore ); + return ( + + ); +}; +``` + +It is also possible to remove panels from the admin using the `removeEditorPanel` function by passing the name of the registered panel. + +```js +import { useDispatch } from '@wordpress/data'; +import { store as editPostStore } from '@wordpress/edit-post'; + +const Example = () => { + const { removeEditorPanel } = useDispatch( editPostStore ); + return ( + ); +}; ``` diff --git a/docs/reference-guides/theme-json-reference/styles-versions.md b/docs/reference-guides/theme-json-reference/styles-versions.md new file mode 100644 index 00000000000000..734c59c3d159ce --- /dev/null +++ b/docs/reference-guides/theme-json-reference/styles-versions.md @@ -0,0 +1,57 @@ +# Available Styles Options + +New styles options are integrated into theme.json on a regular basis. Knowing the style options available through theme.json or the styles editor at any given time can be challenging. To clarify, the table below indicates the WordPress version when each theme.json styles option became available and when a corresponding control was added to the user interface to allow management of the style from the Styles editor. + +## Styles Keys + +| Key | theme.json Since| Style Editor Since | +| --- | :---: | :---: | +| `color.gradient` | 5.8 | 5.9 | +| `color.background` | 5.8 | 5.9 | +| `color.text` | 5.8 | 5.9 | +| `border.color` | 5.9 | 5.9 | +| `border.width` | 5.9 | 5.9 | +| `border.style` | 5.9 | 5.9 | +| `border.radius` | 5.8 | 5.9 | +| `border.radius.topLeft` | 5.9 | 5.9 | +| `border.radius.topRight` | 5.9 | 5.9 | +| `border.radius.bottomLeft` | 5.9 | 5.9 | +| `border.radius.bottomRight` | 5.9 | 5.9 | +| `border.top.color` | 6.1 | 6.1 | +| `border.top.width` | 6.1 | 6.1 | +| `border.top.style` | 6.1 | 6.1 | +| `border.right.color` | 6.1 | 6.1 | +| `border.right.width` | 6.1 | 6.1 | +| `border.right.style` | 6.1 | 6.1 | +| `border.bottom.color` | 6.1 | 6.1 | +| `border.bottom.width` | 6.1 | 6.1 | +| `border.bottom.style` | 6.1 | 6.1 | +| `border.left.color` | 6.1 | 6.1 | +| `border.left.width` | 6.1 | 6.1 | +| `border.left.style` | 6.1 | 6.1 | +| `typography.fontFamily` | 5.9 | 5.9 | +| `typography.fontSize` | 5.8 | 5.9 | +| `typography.fontStyle` | 5.9 | 5.9 | +| `typography.fontWeight` | 5.9 | 5.9 | +| `typography.letterSpacing` | 5.9 | 5.9 | +| `typography.lineHeight` | 5.8 | 5.9 | +| `typography.textDecoration` | 5.9 | 6.2 | +| `typography.textTransform` | 5.9 | 6.0 | +| `spacing.padding` | 5.9 | 5.9 | +| `spacing.padding.top` | 5.8 | 5.9 | +| `spacing.padding.right` | 5.8 | 5.9 | +| `spacing.padding.left` | 5.8 | 5.9 | +| `spacing.padding.bottom` | 5.8 | 5.9 | +| `spacing.margin` | 5.9 | 5.9 | +| `spacing.margin.top` | 5.8 | 5.9 | +| `spacing.margin.right` | 5.8 | 5.9 | +| `spacing.margin.left` | 5.8 | 5.9 | +| `spacing.margin.bottom` | 5.8 | 5.9 | +| `spacing.blockGap` | 5.9 | 5.9 | +| `dimensions.minHeight` | 6.2 | N/A | +| `outline.color` | 6.2 | N/A | +| `outline.offset` | 6.2 | N/A | +| `outline.style` | 6.2 | N/A | +| `outline.width` | 6.2 | N/A | +| `filter.duotone` | 5.9 | N/A | +| `shadow` | 6.1 | 6.2 | diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md index acc0a499ec61b0..505596819bfb4c 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -60,6 +60,7 @@ Settings related to shadows. | Property | Type | Default | Props | | --- | --- | --- |--- | +| defaultPresets | boolean | true | | | presets | array | | name, shadow, slug | --- @@ -106,6 +107,16 @@ Settings related to layout. --- +### position + +Settings related to position. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| sticky | boolean | false | | + +--- + ### spacing Settings related to spacing. @@ -134,6 +145,7 @@ Settings related to typography. | fluid | undefined | false | | | letterSpacing | boolean | true | | | lineHeight | boolean | false | | +| textColumns | boolean | false | | | textDecoration | boolean | true | | | textTransform | boolean | true | | | dropCap | boolean | true | | @@ -180,6 +192,16 @@ Color styles. --- +### dimensions + +Dimensions styles + +| Property | Type | Props | +| --- | --- |--- | +| minHeight | string, object | | + +--- + ### spacing Spacing styles. @@ -204,6 +226,7 @@ Typography styles. | fontWeight | string, object | | | letterSpacing | string, object | | | lineHeight | string, object | | +| textColumns | string | | | textDecoration | string, object | | | textTransform | string, object | | @@ -245,5 +268,36 @@ Sets custom CSS to apply styling not covered by other theme.json properties. --- +## customTemplates + +Additional metadata for custom templates defined in the templates folder. + +Type: `object`. + +| Property | Description | Type | +| --- | --- | --- | +| name | Filename, without extension, of the template in the templates folder. | string | +| title | Title of the template, translatable. | string | +| postTypes | List of post types that can use this custom template. | array | + + +## templateParts + +Additional metadata for template parts defined in the parts folder. + +Type: `object`. + +| Property | Description | Type | +| --- | --- | --- | +| name | Filename, without extension, of the template in the parts folder. | string | +| title | Title of the template, translatable. | string | +| area | The area the template part is used for. Block variations for `header` and `footer` values exist and will be used when the area is set to one of those. | string | + + +## Patterns + +An array of pattern slugs to be registered from the Pattern Directory. +Type: `array`. + diff --git a/docs/reference-guides/theme-json-reference/theme-json-migrations.md b/docs/reference-guides/theme-json-reference/theme-json-migrations.md index cac253f64ffbe9..b043ca1fba52ac 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-migrations.md +++ b/docs/reference-guides/theme-json-reference/theme-json-migrations.md @@ -41,6 +41,7 @@ Additions to settings: - `settings.typography.fontStyle` - `settings.typography.fontWeight` - `settings.typography.letterSpacing` +- `settings.typography.textColumns` - `settings.typography.textDecoration` - `settings.typography.textTransform` @@ -55,6 +56,7 @@ Additions to styles: - `styles.typography.fontStyle` - `styles.typography.fontWeight` - `styles.typography.letterSpacing` +- `styles.typography.textColumns` - `styles.typography.textDecoration` - `styles.typography.textTransform` diff --git a/docs/toc.json b/docs/toc.json index ef9aaf164046ca..7b48e68cbab564 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -179,6 +179,9 @@ { "docs/reference-guides/block-api/block-registration.md": [] }, + { + "docs/reference-guides/block-api/block-selectors.md": [] + }, { "docs/reference-guides/block-api/block-styles.md": [] }, { "docs/reference-guides/block-api/block-supports.md": [] }, { @@ -247,6 +250,9 @@ }, { "docs/reference-guides/theme-json-reference/theme-json-migrations.md": [] + }, + { + "docs/reference-guides/theme-json-reference/styles-versions.md": [] } ] }, diff --git a/gutenberg.php b/gutenberg.php index 0e5f2b3ba9b5fe..612a4325fd2bc0 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,9 +3,9 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * 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.0 + * Requires at least: 6.1 * Requires PHP: 5.6 - * Version: 15.3.0-rc.1 + * Version: 15.8.0 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/lib/block-editor-settings.php b/lib/block-editor-settings.php new file mode 100644 index 00000000000000..c0f514da8d85ef --- /dev/null +++ b/lib/block-editor-settings.php @@ -0,0 +1,82 @@ + 'variables', + '__unstableType' => 'presets', + 'isGlobalStyles' => true, + ), + array( + 'css' => 'presets', + '__unstableType' => 'presets', + 'isGlobalStyles' => true, + ), + ); + foreach ( $presets as $preset_style ) { + $actual_css = gutenberg_get_global_stylesheet( array( $preset_style['css'] ) ); + if ( '' !== $actual_css ) { + $preset_style['css'] = $actual_css; + $global_styles[] = $preset_style; + } + } + + if ( wp_theme_has_theme_json() ) { + $block_classes = array( + 'css' => 'styles', + '__unstableType' => 'theme', + 'isGlobalStyles' => true, + ); + $actual_css = gutenberg_get_global_stylesheet( array( $block_classes['css'] ) ); + if ( '' !== $actual_css ) { + $block_classes['css'] = $actual_css; + $global_styles[] = $block_classes; + } + + /* + * Add the custom CSS as a separate stylesheet so any invalid CSS + * entered by users does not break other global styles. + */ + $global_styles[] = array( + 'css' => gutenberg_get_global_styles_custom_css(), + '__unstableType' => 'user', + 'isGlobalStyles' => true, + ); + } else { + // If there is no `theme.json` file, ensure base layout styles are still available. + $block_classes = array( + 'css' => 'base-layout-styles', + '__unstableType' => 'base-layout', + 'isGlobalStyles' => true, + ); + $actual_css = gutenberg_get_global_stylesheet( array( $block_classes['css'] ) ); + if ( '' !== $actual_css ) { + $block_classes['css'] = $actual_css; + $global_styles[] = $block_classes; + } + } + + $settings['styles'] = array_merge( $global_styles, get_block_editor_theme_styles() ); + + // Copied from get_block_editor_settings() at wordpress-develop/block-editor.php. + $settings['__experimentalFeatures'] = gutenberg_get_global_settings(); + + return $settings; +} +add_filter( 'block_editor_settings_all', 'gutenberg_get_block_editor_settings', PHP_INT_MAX ); diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php index ef950f80ce9448..f6e2b479f350fc 100644 --- a/lib/block-supports/duotone.php +++ b/lib/block-supports/duotone.php @@ -5,17 +5,79 @@ * @package gutenberg */ +// Register the block support. (overrides core one). +WP_Block_Supports::get_instance()->register( + 'duotone', + array( + 'register_attribute' => array( 'WP_Duotone_Gutenberg', 'register_duotone_support' ), + ) +); + +// Set up metadata prior to rendering any blocks. +if ( class_exists( 'WP_Duotone' ) ) { + remove_action( 'wp_loaded', array( 'WP_Duotone', 'set_global_styles_presets' ) ); + remove_action( 'wp_loaded', array( 'WP_Duotone', 'set_global_style_block_names' ) ); +} +add_action( 'wp_loaded', array( 'WP_Duotone_Gutenberg', 'set_global_styles_presets' ), 10 ); +add_action( 'wp_loaded', array( 'WP_Duotone_Gutenberg', 'set_global_style_block_names' ), 10 ); + +// Add classnames to blocks using duotone support. +if ( function_exists( 'wp_render_duotone_support' ) ) { + // Deprecated render function. + remove_filter( 'render_block', 'wp_render_duotone_support' ); +} +if ( class_exists( 'WP_Duotone' ) ) { + remove_filter( 'render_block', array( 'WP_Duotone', 'render_duotone_support' ) ); +} +add_filter( 'render_block', array( 'WP_Duotone_Gutenberg', 'render_duotone_support' ), 10, 2 ); + +// Enqueue styles. +// Block styles (core-block-supports-inline-css) before the style engine (gutenberg_enqueue_stored_styles). +// Global styles (global-styles-inline-css) after the other global styles (gutenberg_enqueue_global_styles). +if ( class_exists( 'WP_Duotone' ) ) { + remove_action( 'wp_enqueue_scripts', array( 'WP_Duotone', 'output_block_styles' ) ); + remove_action( 'wp_enqueue_scripts', array( 'WP_Duotone', 'output_global_styles' ) ); +} +add_action( 'wp_enqueue_scripts', array( 'WP_Duotone_Gutenberg', 'output_block_styles' ), 9 ); +add_action( 'wp_enqueue_scripts', array( 'WP_Duotone_Gutenberg', 'output_global_styles' ), 11 ); + +// Add SVG filters to the footer. Also, for classic themes, output block styles (core-block-supports-inline-css). +if ( class_exists( 'WP_Duotone' ) ) { + remove_action( 'wp_footer', array( 'WP_Duotone', 'output_footer_assets' ) ); +} +add_action( 'wp_footer', array( 'WP_Duotone_Gutenberg', 'output_footer_assets' ), 10 ); + +// Add styles and SVGs for use in the editor via the EditorStyles component. +if ( class_exists( 'WP_Duotone' ) ) { + remove_filter( 'block_editor_settings_all', array( 'WP_Duotone', 'add_editor_settings' ) ); +} +add_filter( 'block_editor_settings_all', array( 'WP_Duotone_Gutenberg', 'add_editor_settings' ), 10 ); + +// Migrate the old experimental duotone support flag. +if ( class_exists( 'WP_Duotone' ) ) { + remove_filter( 'block_type_metadata_settings', array( 'WP_Duotone', 'migrate_experimental_duotone_support_flag' ) ); +} +add_filter( 'block_type_metadata_settings', array( 'WP_Duotone_Gutenberg', 'migrate_experimental_duotone_support_flag' ), 10, 2 ); + +/* + * Deprecated functions below. All new functions should be added in class-wp-duotone-gutenberg.php. + */ + /** * Direct port of tinycolor's bound01 function, lightly simplified to maintain * consistency with tinycolor. * * @see https://github.com/bgrins/TinyColor * + * @deprecated 6.3.0 + * * @param mixed $n Number of unknown type. * @param int $max Upper value of the range to bound to. * @return float Value in the range [0,1]. */ function gutenberg_tinycolor_bound01( $n, $max ) { + _deprecated_function( __FUNCTION__, '6.3.0' ); + if ( 'string' === gettype( $n ) && str_contains( $n, '.' ) && 1 === (float) $n ) { $n = '100%'; } @@ -42,10 +104,14 @@ function gutenberg_tinycolor_bound01( $n, $max ) { * * @see https://github.com/bgrins/TinyColor * + * @deprecated 6.3.0 + * * @param mixed $n Number of unknown type. * @return float Value in the range [0,1]. */ function gutenberg_tinycolor_bound_alpha( $n ) { + _deprecated_function( __FUNCTION__, '6.3.0' ); + if ( is_numeric( $n ) ) { $n = (float) $n; if ( $n >= 0 && $n <= 1 ) { @@ -60,10 +126,14 @@ function gutenberg_tinycolor_bound_alpha( $n ) { * * @see https://github.com/bgrins/TinyColor * + * @deprecated 6.3.0 + * * @param array $rgb_color RGB object. * @return array Rounded and converted RGB object. */ function gutenberg_tinycolor_rgb_to_rgb( $rgb_color ) { + _deprecated_function( __FUNCTION__, '6.3.0' ); + return array( 'r' => gutenberg_tinycolor_bound01( $rgb_color['r'], 255 ) * 255, 'g' => gutenberg_tinycolor_bound01( $rgb_color['g'], 255 ) * 255, @@ -76,12 +146,16 @@ function gutenberg_tinycolor_rgb_to_rgb( $rgb_color ) { * * @see https://github.com/bgrins/TinyColor * + * @deprecated 6.3.0 + * * @param float $p first component. * @param float $q second component. * @param float $t third component. * @return float R, G, or B component. */ function gutenberg_tinycolor_hue_to_rgb( $p, $q, $t ) { + _deprecated_function( __FUNCTION__, '6.3.0' ); + if ( $t < 0 ) { ++$t; } @@ -105,10 +179,14 @@ function gutenberg_tinycolor_hue_to_rgb( $p, $q, $t ) { * * @see https://github.com/bgrins/TinyColor * + * @deprecated 6.3.0 + * * @param array $hsl_color HSL object. * @return array Rounded and converted RGB object. */ function gutenberg_tinycolor_hsl_to_rgb( $hsl_color ) { + _deprecated_function( __FUNCTION__, '6.3.0' ); + $h = gutenberg_tinycolor_bound01( $hsl_color['h'], 360 ); $s = gutenberg_tinycolor_bound01( $hsl_color['s'], 100 ); $l = gutenberg_tinycolor_bound01( $hsl_color['l'], 100 ); @@ -140,10 +218,14 @@ function gutenberg_tinycolor_hsl_to_rgb( $hsl_color ) { * @see https://github.com/bgrins/TinyColor * @see https://github.com/casesandberg/react-color/ * + * @deprecated 6.3.0 + * * @param string $color_str CSS color string. * @return array RGB object. */ function gutenberg_tinycolor_string_to_rgb( $color_str ) { + _deprecated_function( __FUNCTION__, '6.3.0' ); + $color_str = strtolower( trim( $color_str ) ); $css_integer = '[-\\+]?\\d+%?'; @@ -293,271 +375,64 @@ function gutenberg_tinycolor_string_to_rgb( $color_str ) { /** * Returns the prefixed id for the duotone filter for use as a CSS id. * + * @deprecated 6.3.0 + * * @param array $preset Duotone preset value as seen in theme.json. * @return string Duotone filter CSS id. */ function gutenberg_get_duotone_filter_id( $preset ) { - if ( ! isset( $preset['slug'] ) ) { - return ''; - } - - return 'wp-duotone-' . $preset['slug']; + _deprecated_function( __FUNCTION__, '6.3.0' ); + return WP_Duotone_Gutenberg::get_filter_id_from_preset( $preset ); } /** * Returns the CSS filter property url to reference the rendered SVG. * + * @deprecated 6.3.0 + * * @param array $preset Duotone preset value as seen in theme.json. * @return string Duotone CSS filter property url value. */ function gutenberg_get_duotone_filter_property( $preset ) { - if ( isset( $preset['colors'] ) && is_string( $preset['colors'] ) ) { - return $preset['colors']; - } - $filter_id = gutenberg_get_duotone_filter_id( $preset ); - return "url('#" . $filter_id . "')"; + _deprecated_function( __FUNCTION__, '6.3.0' ); + return WP_Duotone_Gutenberg::get_filter_css_property_value_from_preset( $preset ); } /** * Returns the duotone filter SVG string for the preset. * + * @deprecated 6.3.0 + * * @param array $preset Duotone preset value as seen in theme.json. * @return string Duotone SVG filter. */ function gutenberg_get_duotone_filter_svg( $preset ) { - $filter_id = gutenberg_get_duotone_filter_id( $preset ); - - $duotone_values = array( - 'r' => array(), - 'g' => array(), - 'b' => array(), - 'a' => array(), - ); - - if ( ! isset( $preset['colors'] ) || ! is_array( $preset['colors'] ) ) { - $preset['colors'] = array(); - } - - foreach ( $preset['colors'] as $color_str ) { - $color = gutenberg_tinycolor_string_to_rgb( $color_str ); - - $duotone_values['r'][] = $color['r'] / 255; - $duotone_values['g'][] = $color['g'] / 255; - $duotone_values['b'][] = $color['b'] / 255; - $duotone_values['a'][] = $color['a']; - } - - ob_start(); - - ?> - - - - - - - - - - - - - - - - - <', '><', $svg ); - $svg = trim( $svg ); - } - - return $svg; + _deprecated_function( __FUNCTION__, '6.3.0' ); + return WP_Duotone_Gutenberg::get_filter_svg_from_preset( $preset ); } /** * Registers the style and colors block attributes for block types that support it. * + * @deprecated 6.3.0 Use WP_Duotone_Gutenberg::register_duotone_support() instead. + * * @param WP_Block_Type $block_type Block Type. */ function gutenberg_register_duotone_support( $block_type ) { - $has_duotone_support = false; - if ( property_exists( $block_type, 'supports' ) ) { - $has_duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); - } - - if ( $has_duotone_support ) { - if ( ! $block_type->attributes ) { - $block_type->attributes = array(); - } - - if ( ! array_key_exists( 'style', $block_type->attributes ) ) { - $block_type->attributes['style'] = array( - 'type' => 'object', - ); - } - } + _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Duotone_Gutenberg::register_duotone_support' ); + return WP_Duotone_Gutenberg::register_duotone_support( $block_type ); } /** * Render out the duotone stylesheet and SVG. * + * @deprecated 6.3.0 Use WP_Duotone_Gutenberg::render_duotone_support() instead. + * * @param string $block_content Rendered block content. * @param array $block Block object. * @return string Filtered block content. */ function gutenberg_render_duotone_support( $block_content, $block ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); - - $duotone_support = false; - if ( $block_type && property_exists( $block_type, 'supports' ) ) { - $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); - } - - $has_duotone_attribute = isset( $block['attrs']['style']['color']['duotone'] ); - - if ( - ! $duotone_support || - ! $has_duotone_attribute - ) { - return $block_content; - } - - // Possible values for duotone attribute: - // 1. Array of colors - e.g. array('#000000', '#ffffff'). - // 2. Variable for an existing Duotone preset - e.g. 'var:preset|duotone|green-blue'. - // 3. A CSS string - e.g. 'unset' to remove globally applied duotone. - $duotone_attr = $block['attrs']['style']['color']['duotone']; - - $is_preset = is_string( $duotone_attr ) && strpos( $duotone_attr, 'var:preset|duotone|' ) === 0; - $is_css = is_string( $duotone_attr ) && strpos( $duotone_attr, 'var:preset|duotone|' ) === false; - $is_custom = is_array( $duotone_attr ); - - // Generate the pieces needed for rendering a duotone to the page. - if ( $is_preset ) { - // Extract the slug from the preset variable string. - $slug = str_replace( 'var:preset|duotone|', '', $duotone_attr ); - - // Utilize existing preset CSS custom property. - $filter_property = "var(--wp--preset--duotone--$slug)"; - } elseif ( $is_css ) { - // Build a unique slug for the filter based on the CSS value. - $slug = wp_unique_id( sanitize_key( $duotone_attr . '-' ) ); - - // Pass through the CSS value. - $filter_property = $duotone_attr; - } elseif ( $is_custom ) { - // Build a unique slug for the filter based on the array of colors. - $slug = wp_unique_id( sanitize_key( implode( '-', $duotone_attr ) . '-' ) ); - - // This has the same shape as a preset, so it can be used in place of a - // preset when getting the filter property and SVG filter. - $filter_data = array( - 'slug' => $slug, - 'colors' => $duotone_attr, - ); - - // Build a customized CSS filter property for unique slug. - $filter_property = gutenberg_get_duotone_filter_property( $filter_data ); - - // SVG will be output on the page later. - $filter_svg = gutenberg_get_duotone_filter_svg( $filter_data ); - } - - // - Applied as a class attribute to the block wrapper. - // - Used as a selector to apply the filter to the block. - $filter_id = gutenberg_get_duotone_filter_id( array( 'slug' => $slug ) ); - - // Build the CSS selectors to which the filter will be applied. - $selector = WP_Theme_JSON_Gutenberg::scope_selector( '.' . $filter_id, $duotone_support ); - - // Calling gutenberg_style_engine_get_stylesheet_from_css_rules ensures that - // the styles are rendered in an inline for block supports because we're - // using the `context` option to instruct it so. - gutenberg_style_engine_get_stylesheet_from_css_rules( - array( - array( - 'selector' => $selector, - 'declarations' => array( - // !important is needed because these styles - // render before global styles, - // and they should be overriding the duotone - // filters set by global styles. - 'filter' => $filter_property . ' !important', - ), - ), - ), - array( - 'context' => 'block-supports', - ) - ); - - // If we needed to generate an SVG, output it on the page. - if ( isset( $filter_svg ) ) { - add_action( - 'wp_footer', - static function () use ( $filter_svg, $selector ) { - echo $filter_svg; - - /* - * Safari renders elements incorrectly on first paint when the - * SVG filter comes after the content that it is filtering, so - * we force a repaint with a WebKit hack which solves the issue. - */ - global $is_safari; - if ( $is_safari ) { - /* - * Simply accessing el.offsetHeight flushes layout and style - * changes in WebKit without having to wait for setTimeout. - */ - printf( - '', - wp_json_encode( $selector ) - ); - } - } - ); - } - - // Like the layout hook, this assumes the hook only applies to blocks with a single wrapper. - return preg_replace( - '/' . preg_quote( 'class="', '/' ) . '/', - 'class="' . $filter_id . ' ', - $block_content, - 1 - ); + _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Duotone_Gutenberg::render_duotone_support' ); + return WP_Duotone_Gutenberg::render_duotone_support( $block_content, $block ); } - -// Register the block support. -WP_Block_Supports::get_instance()->register( - 'duotone', - array( - 'register_attribute' => 'gutenberg_register_duotone_support', - ) -); - -// Remove WordPress core filter to avoid rendering duplicate support elements. -remove_filter( 'render_block', 'wp_render_duotone_support', 10, 2 ); -add_filter( 'render_block', 'gutenberg_render_duotone_support', 10, 2 ); diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index 5391478010964a..d4502aaa2e4785 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -39,13 +39,18 @@ function gutenberg_render_elements_support( $block_content, $block ) { $link_color = _wp_array_get( $block['attrs'], array( 'style', 'elements', 'link', 'color', 'text' ), null ); } + $hover_link_color = null; + if ( ! empty( $block['attrs'] ) ) { + $hover_link_color = _wp_array_get( $block['attrs'], array( 'style', 'elements', 'link', ':hover', 'color', 'text' ), null ); + } + /* - * For now we only care about link color. + * For now we only care about link colors. * This code in the future when we have a public API * should take advantage of WP_Theme_JSON_Gutenberg::compute_style_properties * and work for any element and style. */ - if ( null === $link_color ) { + if ( null === $link_color && null === $hover_link_color ) { return $block_content; } @@ -94,6 +99,16 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { ) ); + if ( isset( $link_block_styles[':hover'] ) ) { + gutenberg_style_engine_get_styles( + $link_block_styles[':hover'], + array( + 'selector' => ".$class_name a:hover", + 'context' => 'block-supports', + ) + ); + } + return null; } diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index cb9f1573e4e056..87cc4a6cc5f18f 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -83,9 +83,8 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support $wide_max_width_value = $wide_size ? $wide_size : $content_size; // Make sure there is a single CSS rule, and all tags are stripped for security. - // TODO: Use `safecss_filter_attr` instead when the minimum required WP version is >= 6.1. - $all_max_width_value = wp_strip_all_tags( explode( ';', $all_max_width_value )[0] ); - $wide_max_width_value = wp_strip_all_tags( explode( ';', $wide_max_width_value )[0] ); + $all_max_width_value = safecss_filter_attr( explode( ';', $all_max_width_value )[0] ); + $wide_max_width_value = safecss_filter_attr( explode( ';', $wide_max_width_value )[0] ); $margin_left = 'left' === $justify_content ? '0 !important' : 'auto !important'; $margin_right = 'right' === $justify_content ? '0 !important' : 'auto !important'; @@ -280,6 +279,37 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support ); } } + } elseif ( 'grid' === $layout_type ) { + $minimum_column_width = ! empty( $layout['minimumColumnWidth'] ) ? $layout['minimumColumnWidth'] : '12rem'; + + $layout_styles[] = array( + 'selector' => $selector, + 'declarations' => array( 'grid-template-columns' => 'repeat(auto-fill, minmax(min(' . $minimum_column_width . ', 100%), 1fr))' ), + ); + + if ( $has_block_gap_support && isset( $gap_value ) ) { + $combined_gap_value = ''; + $gap_sides = is_array( $gap_value ) ? array( 'top', 'left' ) : array( 'top' ); + + foreach ( $gap_sides as $gap_side ) { + $process_value = is_string( $gap_value ) ? $gap_value : _wp_array_get( $gap_value, array( $gap_side ), $fallback_gap_value ); + // Get spacing CSS variable from preset value if provided. + if ( is_string( $process_value ) && str_contains( $process_value, 'var:preset|spacing|' ) ) { + $index_to_splice = strrpos( $process_value, '|' ) + 1; + $slug = _wp_to_kebab_case( substr( $process_value, $index_to_splice ) ); + $process_value = "var(--wp--preset--spacing--$slug)"; + } + $combined_gap_value .= "$process_value "; + } + $gap_value = trim( $combined_gap_value ); + + if ( null !== $gap_value && ! $should_skip_gap_serialization ) { + $layout_styles[] = array( + 'selector' => $selector, + 'declarations' => array( 'gap' => $gap_value ), + ); + } + } } if ( ! empty( $layout_styles ) ) { @@ -469,6 +499,10 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { } } + // Add combined layout and block classname for global styles to hook onto. + $block_name = explode( '/', $block['blockName'] ); + $class_names[] = 'wp-block-' . end( $block_name ) . '-' . $layout_classname; + $content_with_outer_classnames = ''; if ( ! empty( $outer_class_names ) ) { @@ -545,7 +579,7 @@ function gutenberg_restore_group_inner_container( $block_content, $block ) { if ( wp_theme_has_theme_json() || 1 === preg_match( $group_with_inner_container_regex, $block_content ) || - ( isset( $block['attrs']['layout']['type'] ) && 'flex' === $block['attrs']['layout']['type'] ) + ( isset( $block['attrs']['layout']['type'] ) && ( 'flex' === $block['attrs']['layout']['type'] || 'grid' === $block['attrs']['layout']['type'] ) ) ) { return $block_content; } diff --git a/lib/block-supports/settings.php b/lib/block-supports/settings.php index 0e34c32a8b111a..7c0dd719f41c3a 100644 --- a/lib/block-supports/settings.php +++ b/lib/block-supports/settings.php @@ -85,11 +85,14 @@ function _gutenberg_add_block_level_preset_styles( $pre_render, $block ) { $registry = WP_Block_Type_Registry::get_instance(); $blocks = $registry->get_all_registered(); foreach ( $blocks as $block_type ) { - if ( - isset( $block_type->supports['__experimentalSelector'] ) && - is_string( $block_type->supports['__experimentalSelector'] ) - ) { - $variables_root_selector .= ',' . $block_type->supports['__experimentalSelector']; + // We only want to append selectors for block's using custom selectors + // i.e. not `wp-block-`. + $has_custom_selector = + ( isset( $block_type->supports['__experimentalSelector'] ) && is_string( $block_type->supports['__experimentalSelector'] ) ) || + ( isset( $block_type->selectors['root'] ) && is_string( $block_type->selectors['root'] ) ); + + if ( $has_custom_selector ) { + $variables_root_selector .= ',' . wp_get_block_css_selector( $block_type ); } } $variables_root_selector = WP_Theme_JSON_Gutenberg::scope_selector( $class_name, $variables_root_selector ); @@ -115,7 +118,7 @@ function _gutenberg_add_block_level_preset_styles( $pre_render, $block ) { ) ); - // include preset css classes on the the stylesheet. + // include preset css classes on the stylesheet. $styles .= $theme_json_object->get_stylesheet( array( 'presets' ), null, @@ -131,6 +134,8 @@ function _gutenberg_add_block_level_preset_styles( $pre_render, $block ) { return null; } - +// Remove WordPress core filter to avoid rendering duplicate settings style blocks. +remove_filter( 'render_block', '_wp_add_block_level_presets_class', 10, 2 ); +remove_filter( 'pre_render_block', '_wp_add_block_level_preset_styles', 10, 2 ); add_filter( 'render_block', '_gutenberg_add_block_level_presets_class', 10, 2 ); add_filter( 'pre_render_block', '_gutenberg_add_block_level_preset_styles', 10, 2 ); diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php index c2e033eb176778..f1d29217e38830 100644 --- a/lib/block-supports/typography.php +++ b/lib/block-supports/typography.php @@ -26,6 +26,7 @@ function gutenberg_register_typography_support( $block_type ) { $has_font_weight_support = _wp_array_get( $typography_supports, array( '__experimentalFontWeight' ), false ); $has_letter_spacing_support = _wp_array_get( $typography_supports, array( '__experimentalLetterSpacing' ), false ); $has_line_height_support = _wp_array_get( $typography_supports, array( 'lineHeight' ), false ); + $has_text_columns_support = _wp_array_get( $typography_supports, array( 'textColumns' ), false ); $has_text_decoration_support = _wp_array_get( $typography_supports, array( '__experimentalTextDecoration' ), false ); $has_text_transform_support = _wp_array_get( $typography_supports, array( '__experimentalTextTransform' ), false ); @@ -35,6 +36,7 @@ function gutenberg_register_typography_support( $block_type ) { || $has_font_weight_support || $has_letter_spacing_support || $has_line_height_support + || $has_text_columns_support || $has_text_decoration_support || $has_text_transform_support; @@ -91,6 +93,7 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { $has_font_weight_support = _wp_array_get( $typography_supports, array( '__experimentalFontWeight' ), false ); $has_letter_spacing_support = _wp_array_get( $typography_supports, array( '__experimentalLetterSpacing' ), false ); $has_line_height_support = _wp_array_get( $typography_supports, array( 'lineHeight' ), false ); + $has_text_columns_support = _wp_array_get( $typography_supports, array( 'textColumns' ), false ); $has_text_decoration_support = _wp_array_get( $typography_supports, array( '__experimentalTextDecoration' ), false ); $has_text_transform_support = _wp_array_get( $typography_supports, array( '__experimentalTextTransform' ), false ); @@ -100,6 +103,7 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { $should_skip_font_style = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'fontStyle' ); $should_skip_font_weight = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'fontWeight' ); $should_skip_line_height = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'lineHeight' ); + $should_skip_text_columns = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textColumns' ); $should_skip_text_decoration = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textDecoration' ); $should_skip_text_transform = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textTransform' ); $should_skip_letter_spacing = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'letterSpacing' ); @@ -135,6 +139,10 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { $typography_block_styles['lineHeight'] = _wp_array_get( $block_attributes, array( 'style', 'typography', 'lineHeight' ), null ); } + if ( $has_text_columns_support && ! $should_skip_text_columns && isset( $block_attributes['style']['typography']['textColumns'] ) ) { + $typography_block_styles['textColumns'] = _wp_array_get( $block_attributes, array( 'style', 'typography', 'textColumns' ), null ); + } + if ( $has_text_decoration_support && ! $should_skip_text_decoration && isset( $block_attributes['style']['typography']['textDecoration'] ) ) { $typography_block_styles['textDecoration'] = gutenberg_typography_get_preset_inline_style_value( $block_attributes['style']['typography']['textDecoration'], 'text-decoration' ); @@ -203,44 +211,6 @@ function gutenberg_typography_get_preset_inline_style_value( $style_value, $css_ return sprintf( 'var(--wp--preset--%s--%s);', $css_property, $slug ); } -/** - * This method is no longer used and has been deprecated in Core since 6.1.0. - * - * It can be deleted once Gutenberg's minimum supported WordPress version is >= 6.1 - * - * Generates an inline style for a typography feature e.g. text decoration, - * text transform, and font style. - * - * @since 5.8.0 - * @deprecated 6.1.0 - * - * @param array $attributes Block's attributes. - * @param string $feature Key for the feature within the typography styles. - * @param string $css_property Slug for the CSS property the inline style sets. - * - * @return string CSS inline style. - */ -function gutenberg_typography_get_css_variable_inline_style( $attributes, $feature, $css_property ) { - // Retrieve current attribute value or skip if not found. - $style_value = _wp_array_get( $attributes, array( 'style', 'typography', $feature ), false ); - if ( ! $style_value ) { - return; - } - - // If we don't have a preset CSS variable, we'll assume it's a regular CSS value. - if ( ! str_contains( $style_value, "var:preset|{$css_property}|" ) ) { - return sprintf( '%s:%s;', $css_property, $style_value ); - } - - // We have a preset CSS variable as the style. - // Get the style value from the string and return CSS style. - $index_to_splice = strrpos( $style_value, '|' ) + 1; - $slug = substr( $style_value, $index_to_splice ); - - // Return the actual CSS inline style e.g. `text-decoration:var(--wp--preset--text-decoration--underline);`. - return sprintf( '%s:var(--wp--preset--%s--%s);', $css_property, $css_property, $slug ); -} - /** * Renders typography styles/content to the block wrapper. * @@ -295,12 +265,11 @@ function gutenberg_get_typography_value_and_unit( $raw_value, $options = array() return null; } - // Converts numeric values to pixel values by default. if ( empty( $raw_value ) ) { return null; } - // Converts numbers to pixel values by default. + // Converts numeric values to pixel values by default. if ( is_numeric( $raw_value ) ) { $raw_value = $raw_value . 'px'; } @@ -459,7 +428,10 @@ function gutenberg_get_typography_font_size_value( $preset, $should_use_fluid_ty } // Checks if fluid font sizes are activated. - $typography_settings = gutenberg_get_global_settings( array( 'typography' ) ); + $global_settings = gutenberg_get_global_settings(); + $typography_settings = isset( $global_settings['typography'] ) ? $global_settings['typography'] : array(); + $layout_settings = isset( $global_settings['layout'] ) ? $global_settings['layout'] : array(); + $should_use_fluid_typography = isset( $typography_settings['fluid'] ) && ( true === $typography_settings['fluid'] || is_array( $typography_settings['fluid'] ) ) ? @@ -473,12 +445,13 @@ function gutenberg_get_typography_font_size_value( $preset, $should_use_fluid_ty $fluid_settings = isset( $typography_settings['fluid'] ) && is_array( $typography_settings['fluid'] ) ? $typography_settings['fluid'] : array(); // Defaults. - $default_maximum_viewport_width = '1600px'; - $default_minimum_viewport_width = '768px'; - $default_minimum_font_size_factor = 0.75; - $default_scale_factor = 1; - $has_min_font_size = isset( $fluid_settings['minFontSize'] ) && ! empty( gutenberg_get_typography_value_and_unit( $fluid_settings['minFontSize'] ) ); - $default_minimum_font_size_limit = $has_min_font_size ? $fluid_settings['minFontSize'] : '14px'; + $default_maximum_viewport_width = isset( $layout_settings['wideSize'] ) ? $layout_settings['wideSize'] : '1600px'; + $default_minimum_viewport_width = '320px'; + $default_minimum_font_size_factor_max = 0.75; + $default_minimum_font_size_factor_min = 0.25; + $default_scale_factor = 1; + $has_min_font_size = isset( $fluid_settings['minFontSize'] ) && ! empty( gutenberg_get_typography_value_and_unit( $fluid_settings['minFontSize'] ) ); + $default_minimum_font_size_limit = $has_min_font_size ? $fluid_settings['minFontSize'] : '14px'; // Font sizes. $fluid_font_size_settings = isset( $preset['fluid'] ) ? $preset['fluid'] : null; @@ -530,7 +503,16 @@ function gutenberg_get_typography_font_size_value( $preset, $should_use_fluid_ty * the given font size multiplied by the min font size scale factor. */ if ( ! $minimum_font_size_raw ) { - $calculated_minimum_font_size = round( $preferred_size['value'] * $default_minimum_font_size_factor, 3 ); + $preferred_font_size_in_px = 'px' === $preferred_size['unit'] ? $preferred_size['value'] : $preferred_size['value'] * 16; + + /* + * The scale factor is a multiplier that affects how quickly the curve will move towards the minimum, + * that is, how quickly the size factor reaches 0 given increasing font size values. + * For a - b * log2(), lower values of b will make the curve move towards the minimum faster. + * The scale factor is constrained between min and max values. + */ + $minimum_font_size_factor = min( max( 1 - 0.075 * log( $preferred_font_size_in_px, 2 ), $default_minimum_font_size_factor_min ), $default_minimum_font_size_factor_max ); + $calculated_minimum_font_size = round( $preferred_size['value'] * $minimum_font_size_factor, 3 ); // Only use calculated min font size if it's > $minimum_font_size_limit value. if ( ! empty( $minimum_font_size_limit ) && $calculated_minimum_font_size <= $minimum_font_size_limit['value'] ) { diff --git a/lib/blocks.php b/lib/blocks.php index ddd3d252c75702..bbee108b71c5f5 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -22,6 +22,7 @@ function gutenberg_reregister_core_block_types() { 'column', 'columns', 'comments', + 'details', 'group', 'html', 'list', @@ -134,8 +135,6 @@ function gutenberg_reregister_core_block_types() { $block_folders = $details['block_folders']; $block_names = $details['block_names']; - $registry = WP_Block_Type_Registry::get_instance(); - foreach ( $block_folders as $folder_name ) { $block_json_file = $blocks_dir . $folder_name . '/block.json'; @@ -147,10 +146,7 @@ function gutenberg_reregister_core_block_types() { continue; } - if ( $registry->is_registered( $metadata['name'] ) ) { - $registry->unregister( $metadata['name'] ); - } - + gutenberg_deregister_core_block_and_assets( $metadata['name'] ); gutenberg_register_core_block_assets( $folder_name ); register_block_type_from_metadata( $block_json_file ); } @@ -162,9 +158,7 @@ function gutenberg_reregister_core_block_types() { $sub_block_names_normalized = is_string( $sub_block_names ) ? array( $sub_block_names ) : $sub_block_names; foreach ( $sub_block_names_normalized as $block_name ) { - if ( $registry->is_registered( $block_name ) ) { - $registry->unregister( $block_name ); - } + gutenberg_deregister_core_block_and_assets( $block_name ); gutenberg_register_core_block_assets( $block_name ); } @@ -175,6 +169,28 @@ function gutenberg_reregister_core_block_types() { add_action( 'init', 'gutenberg_reregister_core_block_types' ); +/** + * Deregisters the existing core block type and its assets. + * + * @param string $block_name The name of the block. + * + * @return void + */ +function gutenberg_deregister_core_block_and_assets( $block_name ) { + $registry = WP_Block_Type_Registry::get_instance(); + if ( $registry->is_registered( $block_name ) ) { + $block_type = $registry->get_registered( $block_name ); + if ( ! empty( $block_type->view_script_handles ) ) { + foreach ( $block_type->view_script_handles as $view_script_handle ) { + if ( str_starts_with( $view_script_handle, 'wp-block-' ) ) { + wp_deregister_script( $view_script_handle ); + } + } + } + $registry->unregister( $block_name ); + } +} + /** * Registers block styles for a core block. * diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php new file mode 100644 index 00000000000000..b461e4ba0a64fc --- /dev/null +++ b/lib/class-wp-duotone-gutenberg.php @@ -0,0 +1,1045 @@ + 'blue-orange', + * … + * ] + * + * @var array + */ + private static $global_styles_block_names = array(); + + /** + * An array of duotone filter data from global, theme, and custom presets. + * + * Example: + * [ + * 'wp-duotone-blue-orange' => [ + * 'slug' => 'blue-orange', + * 'colors' => [ '#0000ff', '#ffcc00' ], + * ], + * 'wp-duotone-red-yellow' => [ + * 'slug' => 'red-yellow', + * 'colors' => [ '#cc0000', '#ffff33' ], + * ], + * … + * ] + * + * @var array + */ + private static $global_styles_presets = array(); + + /** + * All of the duotone filter data from presets for CSS custom properties on + * the page. + * + * Example: + * [ + * 'wp-duotone-blue-orange' => [ + * 'slug' => 'blue-orange', + * 'colors' => [ '#0000ff', '#ffcc00' ], + * ], + * … + * ] + * + * @var array + */ + private static $used_global_styles_presets = array(); + + /** + * All of the duotone filter data for SVGs on the page. Includes both + * presets and custom filters. + * + * Example: + * [ + * 'wp-duotone-blue-orange' => [ + * 'slug' => 'blue-orange', + * 'colors' => [ '#0000ff', '#ffcc00' ], + * ], + * 'wp-duotone-000000-ffffff-2' => [ + * 'slug' => '000000-ffffff-2', + * 'colors' => [ '#000000', '#ffffff' ], + * ], + * … + * ] + * + * @var array + */ + private static $used_svg_filter_data = array(); + + /** + * All of the block CSS declarations for styles on the page. + * + * Example: + * [ + * [ + * 'selector' => '.wp-duotone-000000-ffffff-2.wp-block-image img', + * 'declarations' => [ + * 'filter' => 'url(#wp-duotone-000000-ffffff-2)', + * ], + * ], + * … + * ] + * + * @var array + */ + private static $block_css_declarations = array(); + + /** + * Direct port of colord's clamp function. Using min/max instead of + * nested ternaries. + * + * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/helpers.ts#L23 + * + * @param float $number The number to clamp. + * @param float $min The minimum value. + * @param float $max The maximum value. + * @return float The clamped value. + */ + private static function colord_clamp( $number, $min = 0, $max = 1 ) { + return $number > $max ? $max : ( $number > $min ? $number : $min ); + } + + /** + * Direct port of colord's clampHue function. + * + * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/helpers.ts#L32 + * + * @param float $degrees The hue to clamp. + * @return float The clamped hue. + */ + private static function colord_clamp_hue( $degrees ) { + $degrees = is_finite( $degrees ) ? $degrees % 360 : 0; + return $degrees > 0 ? $degrees : $degrees + 360; + } + + /** + * Direct port of colord's parseHue function. + * + * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/helpers.ts#L40 + * + * @param float $value The hue value to parse. + * @param string $unit The unit of the hue value. + * @return float The parsed hue value. + */ + private static function colord_parse_hue( $value, $unit = 'deg' ) { + $angle_units = array( + 'grad' => 360 / 400, + 'turn' => 360, + 'rad' => 360 / ( M_PI * 2 ), + ); + + $factor = $angle_units[ $unit ]; + if ( ! $factor ) { + $factor = 1; + } + + return (float) $value * $factor; + } + + /** + * Direct port of colord's parseHex function. + * + * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hex.ts#L8 + * + * @param string $hex The hex string to parse. + * @return array|null An array of RGBA values or null if the hex string is invalid. + */ + private static function colord_parse_hex( $hex ) { + $is_match = preg_match( + '/^#([0-9a-f]{3,8})$/i', + $hex, + $hex_match + ); + + if ( ! $is_match ) { + return null; + } + + $hex = $hex_match[1]; + + if ( 4 >= strlen( $hex ) ) { + return array( + 'r' => (int) base_convert( $hex[0] . $hex[0], 16, 10 ), + 'g' => (int) base_convert( $hex[1] . $hex[1], 16, 10 ), + 'b' => (int) base_convert( $hex[2] . $hex[2], 16, 10 ), + 'a' => 4 === strlen( $hex ) ? round( base_convert( $hex[3] . $hex[3], 16, 10 ) / 255, 2 ) : 1, + ); + } + + if ( 6 === strlen( $hex ) || 8 === strlen( $hex ) ) { + return array( + 'r' => (int) base_convert( substr( $hex, 0, 2 ), 16, 10 ), + 'g' => (int) base_convert( substr( $hex, 2, 2 ), 16, 10 ), + 'b' => (int) base_convert( substr( $hex, 4, 2 ), 16, 10 ), + 'a' => 8 === strlen( $hex ) ? round( (int) base_convert( substr( $hex, 6, 2 ), 16, 10 ) / 255, 2 ) : 1, + ); + } + + return null; + } + + /** + * Direct port of colord's clampRgba function. + * + * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/rgb.ts#L5 + * + * @param array $rgba The RGBA array to clamp. + * @return array The clamped RGBA array. + */ + private static function colord_clamp_rgba( $rgba ) { + $rgba['r'] = self::colord_clamp( $rgba['r'], 0, 255 ); + $rgba['g'] = self::colord_clamp( $rgba['g'], 0, 255 ); + $rgba['b'] = self::colord_clamp( $rgba['b'], 0, 255 ); + $rgba['a'] = self::colord_clamp( $rgba['a'] ); + + return $rgba; + } + + /** + * Direct port of colord's parseRgbaString function. + * + * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/rgbString.ts#L18 + * + * @param string $input The RGBA string to parse. + * @return array|null An array of RGBA values or null if the RGB string is invalid. + */ + private static function colord_parse_rgba_string( $input ) { + // Functional syntax. + $is_match = preg_match( + '/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*(?:,\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i', + $input, + $match + ); + + if ( ! $is_match ) { + // Whitespace syntax. + $is_match = preg_match( + '/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s*(?:\/\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i', + $input, + $match + ); + } + + if ( ! $is_match ) { + return null; + } + + // For some reason, preg_match doesn't include empty matches at the end + // of the array, so we add them manually to make things easier later. + for ( $i = 1; $i <= 8; $i++ ) { + if ( ! isset( $match[ $i ] ) ) { + $match[ $i ] = ''; + } + } + + if ( $match[2] !== $match[4] || $match[4] !== $match[6] ) { + return null; + } + + return self::colord_clamp_rgba( + array( + 'r' => (float) $match[1] / ( $match[2] ? 100 / 255 : 1 ), + 'g' => (float) $match[3] / ( $match[4] ? 100 / 255 : 1 ), + 'b' => (float) $match[5] / ( $match[6] ? 100 / 255 : 1 ), + 'a' => '' === $match[7] ? 1 : (float) $match[7] / ( $match[8] ? 100 : 1 ), + ) + ); + } + + /** + * Direct port of colord's clampHsla function. + * + * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hsl.ts#L6 + * + * @param array $hsla The HSLA array to clamp. + * @return array The clamped HSLA array. + */ + private static function colord_clamp_hsla( $hsla ) { + $hsla['h'] = self::colord_clamp_hue( $hsla['h'] ); + $hsla['s'] = self::colord_clamp( $hsla['s'], 0, 100 ); + $hsla['l'] = self::colord_clamp( $hsla['l'], 0, 100 ); + $hsla['a'] = self::colord_clamp( $hsla['a'] ); + + return $hsla; + } + + /** + * Direct port of colord's hsvaToRgba function. + * + * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hsv.ts#L52 + * + * @param array $hsva The HSVA array to convert. + * @return array The RGBA array. + */ + private static function colord_hsva_to_rgba( $hsva ) { + $h = ( $hsva['h'] / 360 ) * 6; + $s = $hsva['s'] / 100; + $v = $hsva['v'] / 100; + $a = $hsva['a']; + + $hh = floor( $h ); + $b = $v * ( 1 - $s ); + $c = $v * ( 1 - ( $h - $hh ) * $s ); + $d = $v * ( 1 - ( 1 - $h + $hh ) * $s ); + $module = $hh % 6; + + return array( + 'r' => array( $v, $c, $b, $b, $d, $v )[ $module ] * 255, + 'g' => array( $d, $v, $v, $c, $b, $b )[ $module ] * 255, + 'b' => array( $b, $b, $d, $v, $v, $c )[ $module ] * 255, + 'a' => $a, + ); + } + + /** + * Direct port of colord's hslaToHsva function. + * + * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hsl.ts#L33 + * + * @param array $hsla The HSLA array to convert. + * @return array The HSVA array. + */ + private static function colord_hsla_to_hsva( $hsla ) { + $h = $hsla['h']; + $s = $hsla['s']; + $l = $hsla['l']; + $a = $hsla['a']; + + $s *= ( $l < 50 ? $l : 100 - $l ) / 100; + + return array( + 'h' => $h, + 's' => $s > 0 ? ( ( 2 * $s ) / ( $l + $s ) ) * 100 : 0, + 'v' => $l + $s, + 'a' => $a, + ); + } + + /** + * Direct port of colord's hslaToRgba function. + * + * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hsl.ts#L55 + * + * @param array $hsla The HSLA array to convert. + * @return array The RGBA array. + */ + private static function colord_hsla_to_rgba( $hsla ) { + return self::colord_hsva_to_rgba( self::colord_hsla_to_hsva( $hsla ) ); + } + + /** + * Direct port of colord's parseHslaString function. + * + * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hslString.ts#L17 + * + * @param string $input The HSLA string to parse. + * @return array|null An array of RGBA values or null if the RGB string is invalid. + */ + private static function colord_parse_hsla_string( $input ) { + // Functional syntax. + $is_match = preg_match( + '/^hsla?\(\s*([+-]?\d*\.?\d+)(deg|rad|grad|turn)?\s*,\s*([+-]?\d*\.?\d+)%\s*,\s*([+-]?\d*\.?\d+)%\s*(?:,\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i', + $input, + $match + ); + + if ( ! $is_match ) { + // Whitespace syntax. + $is_match = preg_match( + '/^hsla?\(\s*([+-]?\d*\.?\d+)(deg|rad|grad|turn)?\s+([+-]?\d*\.?\d+)%\s+([+-]?\d*\.?\d+)%\s*(?:\/\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i', + $input, + $match + ); + } + + if ( ! $is_match ) { + return null; + } + + // For some reason, preg_match doesn't include empty matches at the end + // of the array, so we add them manually to make things easier later. + for ( $i = 1; $i <= 6; $i++ ) { + if ( ! isset( $match[ $i ] ) ) { + $match[ $i ] = ''; + } + } + + $hsla = self::colord_clamp_hsla( + array( + 'h' => self::colord_parse_hue( $match[1], $match[2] ), + 's' => (float) $match[3], + 'l' => (float) $match[4], + 'a' => '' === $match[5] ? 1 : (float) $match[5] / ( $match[6] ? 100 : 1 ), + ) + ); + + return self::colord_hsla_to_rgba( $hsla ); + } + + /** + * Direct port of colord's parse function simplified for our use case. This + * version only supports string parsing and only returns RGBA values. + * + * @see https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/parse.ts#L37 + * + * @param string $input The string to parse. + * @return array|null An array of RGBA values or null if the string is invalid. + */ + private static function colord_parse( $input ) { + $result = self::colord_parse_hex( $input ); + + if ( ! $result ) { + $result = self::colord_parse_rgba_string( $input ); + } + + if ( ! $result ) { + $result = self::colord_parse_hsla_string( $input ); + } + + return $result; + } + + /** + * Take the inline CSS duotone variable from a block and return the slug. Handles styles slugs like: + * var:preset|duotone|blue-orange + * var(--wp--preset--duotone--blue-orange) + * + * @param string $duotone_attr The duotone attribute from a block. + * @return string The slug of the duotone preset or an empty string if no slug is found. + */ + private static function get_slug_from_attribute( $duotone_attr ) { + // Uses Branch Reset Groups `(?|…)` to return one capture group. + preg_match( '/(?|var:preset\|duotone\|(\S+)|var\(--wp--preset--duotone--(\S+)\))/', $duotone_attr, $matches ); + + return ! empty( $matches[1] ) ? $matches[1] : ''; + } + + /** + * Check if we have a valid duotone preset. + * + * @param string $duotone_attr The duotone attribute from a block. + * @return bool True if the duotone preset present and valid. + */ + private static function is_preset( $duotone_attr ) { + $slug = self::get_slug_from_attribute( $duotone_attr ); + $filter_id = self::get_filter_id( $slug ); + + return array_key_exists( $filter_id, self::$global_styles_presets ); + } + + /** + * Get the CSS variable name for a duotone preset. + * + * @param string $slug The slug of the duotone preset. + * @return string The CSS variable name. + */ + private static function get_css_custom_property_name( $slug ) { + return "--wp--preset--duotone--$slug"; + } + + /** + * Get the ID of the duotone filter. + * + * @param string $slug The slug of the duotone preset. + * @return string The ID of the duotone filter. + */ + private static function get_filter_id( $slug ) { + return "wp-duotone-$slug"; + } + + /** + * Get the CSS variable for a duotone preset. + * + * @param string $slug The slug of the duotone preset. + * @return string The CSS variable. + */ + private static function get_css_var( $slug ) { + $name = self::get_css_custom_property_name( $slug ); + return "var($name)"; + } + + /** + * Get the URL for a duotone filter. + * + * @param string $filter_id The ID of the filter. + * @return string The URL for the duotone filter. + */ + private static function get_filter_url( $filter_id ) { + return "url(#$filter_id)"; + } + + /** + * Gets the SVG for the duotone filter definition. + * + * @param string $filter_id The ID of the filter. + * @param array $colors An array of color strings. + * @return string An SVG with a duotone filter definition. + */ + private static function get_filter_svg( $filter_id, $colors ) { + $duotone_values = array( + 'r' => array(), + 'g' => array(), + 'b' => array(), + 'a' => array(), + ); + + foreach ( $colors as $color_str ) { + $color = self::colord_parse( $color_str ); + + $duotone_values['r'][] = $color['r'] / 255; + $duotone_values['g'][] = $color['g'] / 255; + $duotone_values['b'][] = $color['b'] / 255; + $duotone_values['a'][] = $color['a']; + } + + ob_start(); + + ?> + + + + + + + + + + + + + + + + + <', '><', $svg ); + $svg = trim( $svg ); + } + + return $svg; + } + + /** + * Get the SVGs for the duotone filters. + * + * Example output: + * + * + * @param array $sources The duotone presets. + * @return string The SVGs for the duotone filters. + */ + private static function get_svg_definitions( $sources ) { + $svgs = ''; + foreach ( $sources as $filter_id => $filter_data ) { + $colors = $filter_data['colors']; + $svgs .= self::get_filter_svg( $filter_id, $colors ); + } + return $svgs; + } + + /** + * Get the CSS for global styles. + * + * Example output: + * body{--wp--preset--duotone--blue-orange:url('#wp-duotone-blue-orange');} + * + * @param array $sources The duotone presets. + * @return string The CSS for global styles. + */ + private static function get_global_styles_presets( $sources ) { + $css = 'body{'; + foreach ( $sources as $filter_id => $filter_data ) { + $slug = $filter_data['slug']; + $colors = $filter_data['colors']; + $css_property_name = self::get_css_custom_property_name( $slug ); + $declaration_value = is_string( $colors ) ? $colors : self::get_filter_url( $filter_id ); + $css .= "$css_property_name:$declaration_value;"; + } + $css .= '}'; + return $css; + } + + /** + * Get the CSS selector for a block type. + * + * @param string $block_name The block name. + * + * @return string The CSS selector or null if there is no support. + */ + private static function get_selector( $block_name ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); + + if ( $block_type && property_exists( $block_type, 'supports' ) ) { + // Backwards compatibility with `supports.color.__experimentalDuotone` + // is provided via the `block_type_metadata_settings` filter. If + // `supports.filter.duotone` has not been set and the experimental + // property has been, the experimental property value is copied into + // `supports.filter.duotone`. + $duotone_support = _wp_array_get( $block_type->supports, array( 'filter', 'duotone' ), false ); + if ( ! $duotone_support ) { + return null; + } + + // If the experimental duotone support was set, that value is to be + // treated as a selector and requires scoping. + $experimental_duotone = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); + if ( $experimental_duotone ) { + $root_selector = wp_get_block_css_selector( $block_type ); + return is_string( $experimental_duotone ) + ? WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $experimental_duotone ) + : $root_selector; + } + + // Regular filter.duotone support uses filter.duotone selectors with fallbacks. + return wp_get_block_css_selector( $block_type, array( 'filter', 'duotone' ), true ); + } + } + + /** + * Enqueue a block CSS declaration for the page. + * + * @param string $filter_id The filter ID. e.g. 'wp-duotone-000000-ffffff-2'. + * @param string $duotone_selector The block's duotone selector. e.g. '.wp-block-image img'. + * @param string $filter_value The filter CSS value. e.g. 'url(#wp-duotone-000000-ffffff-2)' or 'unset'. + */ + private static function enqueue_block_css( $filter_id, $duotone_selector, $filter_value ) { + // Build the CSS selectors to which the filter will be applied. + $selectors = explode( ',', $duotone_selector ); + + $selectors_scoped = array(); + foreach ( $selectors as $selector_part ) { + // Assuming the selector part is a subclass selector (not a tag name) + // so we can prepend the filter id class. If we want to support elements + // such as `img` or namespaces, we'll need to add a case for that here. + $selectors_scoped[] = '.' . $filter_id . trim( $selector_part ); + } + + $selector = implode( ', ', $selectors_scoped ); + + self::$block_css_declarations[] = array( + 'selector' => $selector, + 'declarations' => array( + 'filter' => $filter_value, + ), + ); + } + + /** + * Enqueue custom filter assets for the page. Includes an SVG filter and block CSS declaration. + * + * @param string $filter_id The filter ID. e.g. 'wp-duotone-000000-ffffff-2'. + * @param string $duotone_selector The block's duotone selector. e.g. '.wp-block-image img'. + * @param string $filter_value The filter CSS value. e.g. 'url(#wp-duotone-000000-ffffff-2)' or 'unset'. + * @param array $filter_data Duotone filter data with 'slug' and 'colors' keys. + */ + private static function enqueue_custom_filter( $filter_id, $duotone_selector, $filter_value, $filter_data ) { + self::$used_svg_filter_data[ $filter_id ] = $filter_data; + self::enqueue_block_css( $filter_id, $duotone_selector, $filter_value ); + } + + /** + * Enqueue preset assets for the page. Includes a CSS custom property, SVG filter, and block CSS declaration. + * + * @param string $filter_id The filter ID. e.g. 'wp-duotone-blue-orange'. + * @param string $duotone_selector The block's duotone selector. e.g. '.wp-block-image img'. + * @param string $filter_value The filter CSS value. e.g. 'url(#wp-duotone-blue-orange)' or 'unset'. + */ + private static function enqueue_global_styles_preset( $filter_id, $duotone_selector, $filter_value ) { + self::$used_global_styles_presets[ $filter_id ] = self::$global_styles_presets[ $filter_id ]; + self::enqueue_custom_filter( $filter_id, $duotone_selector, $filter_value, self::$global_styles_presets[ $filter_id ] ); + } + + /** + * Registers the style and colors block attributes for block types that support it. + * + * @since 6.3.0 + * + * @param WP_Block_Type $block_type Block Type. + */ + public static function register_duotone_support( $block_type ) { + $has_duotone_support = false; + if ( property_exists( $block_type, 'supports' ) ) { + // Previous `color.__experimentalDuotone` support flag is migrated + // to `filter.duotone` via `block_type_metadata_settings` filter. + $has_duotone_support = _wp_array_get( $block_type->supports, array( 'filter', 'duotone' ), null ); + } + + if ( $has_duotone_support ) { + if ( ! $block_type->attributes ) { + $block_type->attributes = array(); + } + + if ( ! array_key_exists( 'style', $block_type->attributes ) ) { + $block_type->attributes['style'] = array( + 'type' => 'object', + ); + } + } + } + + /** + * Get all possible duotone presets from global and theme styles and store as slug => [ colors array ] + * We only want to process this one time. On block render we'll access and output only the needed presets for that page. + * + * @since 6.3.0 + */ + public static function set_global_styles_presets() { + // Get the per block settings from the theme.json. + $tree = gutenberg_get_global_settings(); + $presets_by_origin = _wp_array_get( $tree, array( 'color', 'duotone' ), array() ); + + foreach ( $presets_by_origin as $presets ) { + foreach ( $presets as $preset ) { + $filter_id = self::get_filter_id( _wp_to_kebab_case( $preset['slug'] ) ); + + self::$global_styles_presets[ $filter_id ] = $preset; + } + } + } + + /** + * Scrape all block names from global styles and store in self::$global_styles_block_names + * + * @since 6.3.0 + */ + public static function set_global_style_block_names() { + // Get the per block settings from the theme.json. + $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); + $block_nodes = $tree->get_styles_block_nodes(); + $theme_json = $tree->get_raw_data(); + + foreach ( $block_nodes as $block_node ) { + // This block definition doesn't include any duotone settings. Skip it. + if ( empty( $block_node['duotone'] ) ) { + continue; + } + + // Value looks like this: 'var(--wp--preset--duotone--blue-orange)' or 'var:preset|duotone|blue-orange'. + $duotone_attr_path = array_merge( $block_node['path'], array( 'filter', 'duotone' ) ); + $duotone_attr = _wp_array_get( $theme_json, $duotone_attr_path, array() ); + + if ( empty( $duotone_attr ) ) { + continue; + } + // If it has a duotone filter preset, save the block name and the preset slug. + $slug = self::get_slug_from_attribute( $duotone_attr ); + + if ( $slug && $slug !== $duotone_attr ) { + self::$global_styles_block_names[ $block_node['name'] ] = $slug; + } + } + } + + /** + * Render out the duotone CSS styles and SVG. + * + * @since 6.3.0 + * + * @param string $block_content Rendered block content. + * @param array $block Block object. + * @return string Filtered block content. + */ + public static function render_duotone_support( $block_content, $block ) { + $duotone_selector = self::get_selector( $block['blockName'] ); + + // The block should have a duotone attribute or have duotone defined in its theme.json to be processed. + $has_duotone_attribute = isset( $block['attrs']['style']['color']['duotone'] ); + $has_global_styles_duotone = array_key_exists( $block['blockName'], self::$global_styles_block_names ); + + if ( + empty( $block_content ) || + ! $duotone_selector || + ( ! $has_duotone_attribute && ! $has_global_styles_duotone ) + ) { + return $block_content; + } + + // Generate the pieces needed for rendering a duotone to the page. + if ( $has_duotone_attribute ) { + + // Possible values for duotone attribute: + // 1. Array of colors - e.g. array('#000000', '#ffffff'). + // 2. Variable for an existing Duotone preset - e.g. 'var:preset|duotone|blue-orange' or 'var(--wp--preset--duotone--blue-orange)'' + // 3. A CSS string - e.g. 'unset' to remove globally applied duotone. + + $duotone_attr = $block['attrs']['style']['color']['duotone']; + $is_preset = is_string( $duotone_attr ) && self::is_preset( $duotone_attr ); + $is_css = is_string( $duotone_attr ) && ! $is_preset; + $is_custom = is_array( $duotone_attr ); + + if ( $is_preset ) { + $slug = self::get_slug_from_attribute( $duotone_attr ); // e.g. 'blue-orange'. + $filter_id = self::get_filter_id( $slug ); // e.g. 'wp-duotone-filter-blue-orange'. + $filter_value = self::get_css_var( $slug ); // e.g. 'var(--wp--preset--duotone--blue-orange)'. + + // CSS custom property, SVG filter, and block CSS. + self::enqueue_global_styles_preset( $filter_id, $duotone_selector, $filter_value ); + + } elseif ( $is_css ) { + $slug = wp_unique_id( sanitize_key( $duotone_attr . '-' ) ); // e.g. 'unset-1'. + $filter_id = self::get_filter_id( $slug ); // e.g. 'wp-duotone-filter-unset-1'. + $filter_value = $duotone_attr; // e.g. 'unset'. + + // Just block CSS. + self::enqueue_block_css( $filter_id, $duotone_selector, $filter_value ); + } elseif ( $is_custom ) { + $slug = wp_unique_id( sanitize_key( implode( '-', $duotone_attr ) . '-' ) ); // e.g. '000000-ffffff-2'. + $filter_id = self::get_filter_id( $slug ); // e.g. 'wp-duotone-filter-000000-ffffff-2'. + $filter_value = self::get_filter_url( $filter_id ); // e.g. 'url(#wp-duotone-filter-000000-ffffff-2)'. + $filter_data = array( + 'slug' => $slug, + 'colors' => $duotone_attr, + ); + + // SVG filter and block CSS. + self::enqueue_custom_filter( $filter_id, $duotone_selector, $filter_value, $filter_data ); + } + } elseif ( $has_global_styles_duotone ) { + $slug = self::$global_styles_block_names[ $block['blockName'] ]; // e.g. 'blue-orange'. + $filter_id = self::get_filter_id( $slug ); // e.g. 'wp-duotone-filter-blue-orange'. + $filter_value = self::get_css_var( $slug ); // e.g. 'var(--wp--preset--duotone--blue-orange)'. + + // CSS custom property, SVG filter, and block CSS. + self::enqueue_global_styles_preset( $filter_id, $duotone_selector, $filter_value ); + } + + // Like the layout hook, this assumes the hook only applies to blocks with a single wrapper. + $tags = new WP_HTML_Tag_Processor( $block_content ); + if ( $tags->next_tag() ) { + $tags->add_class( $filter_id ); + } + + return $tags->get_updated_html(); + } + + /** + * Appends the used block duotone filter declarations to the inline block supports CSS. + * + * @since 6.3.0 + */ + public static function output_block_styles() { + if ( ! empty( self::$block_css_declarations ) ) { + gutenberg_style_engine_get_stylesheet_from_css_rules( + self::$block_css_declarations, + array( + 'context' => 'block-supports', + ) + ); + } + } + + /** + * Appends the used global style duotone filter presets (CSS custom + * properties) to the inline global styles CSS. + * + * @since 6.3.0 + */ + public static function output_global_styles() { + if ( ! empty( self::$used_global_styles_presets ) ) { + wp_add_inline_style( 'global-styles', self::get_global_styles_presets( self::$used_global_styles_presets ) ); + } + } + + /** + * Outputs all necessary SVG for duotone filters, CSS for classic themes. + * + * @since 6.3.0 + */ + public static function output_footer_assets() { + if ( ! empty( self::$used_svg_filter_data ) ) { + echo self::get_svg_definitions( self::$used_svg_filter_data ); + } + + // This is for classic themes - in block themes, the CSS is added in the head via wp_add_inline_style in the wp_enqueue_scripts action. + if ( ! wp_is_block_theme() && ! empty( self::$used_global_styles_presets ) ) { + wp_add_inline_style( 'core-block-supports', self::get_global_styles_presets( self::$used_global_styles_presets ) ); + } + } + + /** + * Adds the duotone SVGs and CSS custom properties to the editor settings so + * they can be pulled in by the EditorStyles component in JS and rendered in + * the post editor. + * + * @since 6.3.0 + * + * @param array $settings The block editor settings from the `block_editor_settings_all` filter. + * @return array The editor settings with duotone SVGs and CSS custom properties. + */ + public static function add_editor_settings( $settings ) { + if ( ! empty( self::$global_styles_presets ) ) { + if ( ! isset( $settings['styles'] ) ) { + $settings['styles'] = array(); + } + + $settings['styles'][] = array( + // For the editor we can add all of the presets by default. + 'assets' => self::get_svg_definitions( self::$global_styles_presets ), + // The 'svgs' type is new in 6.3 and requires the corresponding JS changes in the EditorStyles component to work. + '__unstableType' => 'svgs', + // These styles not generated by global styles, so this must be false or they will be stripped out in gutenberg_get_block_editor_settings. + 'isGlobalStyles' => false, + ); + + $settings['styles'][] = array( + // For the editor we can add all of the presets by default. + 'css' => self::get_global_styles_presets( self::$global_styles_presets ), + // This must be set and must be something other than 'theme' or they will be stripped out in the post editor component. + '__unstableType' => 'presets', + // These styles are no longer generated by global styles, so this must be false or they will be stripped out in gutenberg_get_block_editor_settings. + 'isGlobalStyles' => false, + ); + } + + return $settings; + } + + /** + * Migrate the old experimental duotone support flag to its stabilized location + * under `supports.filter.duotone` and sets. + * + * @since 6.3.0 + * + * @param array $settings Current block type settings. + * @param array $metadata Block metadata as read in via block.json. + * + * @return array Filtered block type settings. + */ + public static function migrate_experimental_duotone_support_flag( $settings, $metadata ) { + $duotone_support = _wp_array_get( $metadata, array( 'supports', 'color', '__experimentalDuotone' ), null ); + + if ( ! isset( $settings['supports']['filter']['duotone'] ) && null !== $duotone_support ) { + _wp_array_set( $settings, array( 'supports', 'filter', 'duotone' ), (bool) $duotone_support ); + } + + return $settings; + } + + /** + * Returns the prefixed id for the duotone filter for use as a CSS id. + * + * Exported for the deprecated function gutenberg_get_duotone_filter_id(). + * + * @since 6.3.0 + * @deprecated 6.3.0 + * + * @param array $preset Duotone preset value as seen in theme.json. + * @return string Duotone filter CSS id. + */ + public static function get_filter_id_from_preset( $preset ) { + _deprecated_function( __FUNCTION__, '6.3.0' ); + + $filter_id = ''; + if ( isset( $preset['slug'] ) ) { + $filter_id = self::get_filter_id( $preset['slug'] ); + } + return $filter_id; + } + + /** + * Gets the SVG for the duotone filter definition from a preset. + * + * Exported for the deprecated function gutenberg_get_duotone_filter_property(). + * + * @since 6.3.0 + * @deprecated 6.3.0 + * + * @param array $preset The duotone preset. + * @return string The SVG for the filter definition. + */ + public static function get_filter_svg_from_preset( $preset ) { + _deprecated_function( __FUNCTION__, '6.3.0' ); + + $filter_id = self::get_filter_id_from_preset( $preset ); + return self::get_filter_svg( $filter_id, $preset['colors'] ); + } + + /** + * Gets the CSS filter property value from a preset. + * + * Exported for the deprecated function gutenberg_get_duotone_filter_id(). + * + * @since 6.3.0 + * @deprecated 6.3.0 + * + * @param array $preset The duotone preset. + * @return string The CSS filter property value. + */ + public static function get_filter_css_property_value_from_preset( $preset ) { + _deprecated_function( __FUNCTION__, '6.3.0' ); + + if ( isset( $preset['colors'] ) && is_string( $preset['colors'] ) ) { + return $preset['colors']; + } + + $filter_id = self::get_filter_id_from_preset( $preset ); + return 'url(#' . $filter_id . ')'; + } +} diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-data-gutenberg.php b/lib/class-wp-theme-json-data-gutenberg.php similarity index 73% rename from lib/compat/wordpress-6.1/class-wp-theme-json-data-gutenberg.php rename to lib/class-wp-theme-json-data-gutenberg.php index 9add04bb65b097..2e5ea474346a92 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-data-gutenberg.php +++ b/lib/class-wp-theme-json-data-gutenberg.php @@ -1,18 +1,21 @@ Disables override of default presets by theme presets. * The relationship between whether to override the defaults * and whether the defaults are enabled is inverse: - * - If defaults are enabled => theme presets should not be overriden - * - If defaults are disabled => theme presets should be overriden + * - If defaults are enabled => theme presets should not be overridden + * - If defaults are disabled => theme presets should be overridden * For example, a theme sets defaultPalette to false, * making the default palette hidden from the user. * In that case, we want all the theme presets to be present, @@ -144,8 +144,8 @@ class WP_Theme_JSON_Gutenberg { 'path' => array( 'color', 'duotone' ), 'prevent_override' => array( 'color', 'defaultDuotone' ), 'use_default_names' => false, - 'value_func' => 'gutenberg_get_duotone_filter_property', - 'css_vars' => '--wp--preset--duotone--$slug', + 'value_func' => null, // CSS Custom Properties for duotone are handled by block supports in class-wp-duotone-gutenberg.php. + 'css_vars' => null, 'classes' => array(), 'properties' => array( 'filter' ), ), @@ -229,6 +229,7 @@ class WP_Theme_JSON_Gutenberg { 'border-left-width' => array( 'border', 'left', 'width' ), 'border-left-style' => array( 'border', 'left', 'style' ), 'color' => array( 'color', 'text' ), + 'column-count' => array( 'typography', 'textColumns' ), 'font-family' => array( 'typography', 'fontFamily' ), 'font-size' => array( 'typography', 'fontSize' ), 'font-style' => array( 'typography', 'fontStyle' ), @@ -357,6 +358,9 @@ class WP_Theme_JSON_Gutenberg { 'duotone' => null, 'gradients' => null, 'link' => null, + 'heading' => null, + 'button' => null, + 'caption' => null, 'palette' => null, 'text' => null, ), @@ -396,6 +400,7 @@ class WP_Theme_JSON_Gutenberg { 'fontWeight' => null, 'letterSpacing' => null, 'lineHeight' => null, + 'textColumns' => null, 'textDecoration' => null, 'textTransform' => null, ), @@ -456,6 +461,7 @@ class WP_Theme_JSON_Gutenberg { 'fontWeight' => null, 'letterSpacing' => null, 'lineHeight' => null, + 'textColumns' => null, 'textDecoration' => null, 'textTransform' => null, ), @@ -556,6 +562,9 @@ public static function get_element_class_name( $element ) { array( 'border', 'style' ), array( 'border', 'width' ), array( 'color', 'link' ), + array( 'color', 'heading' ), + array( 'color', 'button' ), + array( 'color', 'caption' ), array( 'dimensions', 'minHeight' ), // BEGIN EXPERIMENTAL. // Allow `position.fixed` to be opted-in by default. @@ -597,8 +606,15 @@ public function __construct( $theme_json = array(), $origin = 'theme' ) { $registry = WP_Block_Type_Registry::get_instance(); $valid_block_names = array_keys( $registry->get_all_registered() ); $valid_element_names = array_keys( static::ELEMENTS ); - $theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names ); - $this->theme_json = static::maybe_opt_in_into_settings( $theme_json ); + $valid_variations = array(); + foreach ( self::get_blocks_metadata() as $block_name => $block_meta ) { + if ( ! isset( $block_meta['styleVariations'] ) ) { + continue; + } + $valid_variations[ $block_name ] = array_keys( $block_meta['styleVariations'] ); + } + $theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names, $valid_variations ); + $this->theme_json = static::maybe_opt_in_into_settings( $theme_json ); // Internally, presets are keyed by origin. $nodes = static::get_setting_nodes( $this->theme_json ); @@ -676,9 +692,10 @@ protected static function do_opt_in_into_settings( &$context ) { * @param array $input Structure to sanitize. * @param array $valid_block_names List of valid block names. * @param array $valid_element_names List of valid element names. + * @param array $valid_variations List of valid variations per block. * @return array The sanitized output. */ - protected static function sanitize( $input, $valid_block_names, $valid_element_names ) { + protected static function sanitize( $input, $valid_block_names, $valid_element_names, $valid_variations ) { $output = array(); @@ -736,9 +753,13 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n $style_variation_names = array(); if ( ! empty( $input['styles']['blocks'][ $block ]['variations'] ) && - is_array( $input['styles']['blocks'][ $block ]['variations'] ) + is_array( $input['styles']['blocks'][ $block ]['variations'] ) && + isset( $valid_variations[ $block ] ) ) { - $style_variation_names = array_keys( $input['styles']['blocks'][ $block ]['variations'] ); + $style_variation_names = array_intersect( + array_keys( $input['styles']['blocks'][ $block ]['variations'] ), + $valid_variations[ $block ] + ); } $schema_styles_variations = array(); @@ -774,7 +795,7 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n if ( empty( $result ) ) { unset( $output[ $subtree ] ); } else { - $output[ $subtree ] = $result; + $output[ $subtree ] = static::resolve_custom_css_format( $result ); } } @@ -797,7 +818,7 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n * @return string The new selector. */ protected static function append_to_selector( $selector, $to_append, $position = 'right' ) { - if ( ! str_contains( ',', $selector ) ) { + if ( ! str_contains( $selector, ',' ) ) { return 'right' === $position ? $selector . $to_append : $to_append . $selector; } $new_selectors = array(); @@ -850,56 +871,33 @@ protected static function get_blocks_metadata() { } foreach ( $blocks as $block_name => $block_type ) { - if ( - isset( $block_type->supports['__experimentalSelector'] ) && - is_string( $block_type->supports['__experimentalSelector'] ) - ) { - static::$blocks_metadata[ $block_name ]['selector'] = $block_type->supports['__experimentalSelector']; - } else { - static::$blocks_metadata[ $block_name ]['selector'] = '.wp-block-' . str_replace( '/', '-', str_replace( 'core/', '', $block_name ) ); - } + $root_selector = wp_get_block_css_selector( $block_type ); - if ( - isset( $block_type->supports['color']['__experimentalDuotone'] ) && - is_string( $block_type->supports['color']['__experimentalDuotone'] ) - ) { - static::$blocks_metadata[ $block_name ]['duotone'] = $block_type->supports['color']['__experimentalDuotone']; + static::$blocks_metadata[ $block_name ]['selector'] = $root_selector; + static::$blocks_metadata[ $block_name ]['selectors'] = static::get_block_selectors( $block_type, $root_selector ); + + $elements = static::get_block_element_selectors( $root_selector ); + if ( ! empty( $elements ) ) { + static::$blocks_metadata[ $block_name ]['elements'] = $elements; } - // Generate block support feature level selectors if opted into - // for the current block. - $features = array(); - foreach ( static::BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS as $key => $feature ) { - if ( - isset( $block_type->supports[ $key ]['__experimentalSelector'] ) && - $block_type->supports[ $key ]['__experimentalSelector'] - ) { - $features[ $feature ] = static::scope_selector( - static::$blocks_metadata[ $block_name ]['selector'], - $block_type->supports[ $key ]['__experimentalSelector'] - ); + // The block may or may not have a duotone selector. + $duotone_selector = wp_get_block_css_selector( $block_type, 'filter.duotone' ); + + // Keep backwards compatibility for support.color.__experimentalDuotone. + if ( null === $duotone_selector ) { + $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), null ); + + if ( $duotone_support ) { + $root_selector = wp_get_block_css_selector( $block_type ); + $duotone_selector = WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_support ); } } - if ( ! empty( $features ) ) { - static::$blocks_metadata[ $block_name ]['features'] = $features; + if ( null !== $duotone_selector ) { + static::$blocks_metadata[ $block_name ]['duotone'] = $duotone_selector; } - // Assign defaults, then overwrite those that the block sets by itself. - // If the block selector is compounded, will append the element to each - // individual block selector. - $block_selectors = explode( ',', static::$blocks_metadata[ $block_name ]['selector'] ); - foreach ( static::ELEMENTS as $el_name => $el_selector ) { - $element_selector = array(); - foreach ( $block_selectors as $selector ) { - if ( $selector === $el_selector ) { - $element_selector = array( $el_selector ); - break; - } - $element_selector[] = static::append_to_selector( $el_selector, $selector . ' ', 'left' ); - } - static::$blocks_metadata[ $block_name ]['elements'][ $el_name ] = implode( ',', $element_selector ); - } // If the block has style variations, append their selectors to the block metadata. if ( ! empty( $block_type->styles ) ) { $style_selectors = array(); @@ -1312,7 +1310,7 @@ protected function get_layout_styles( $block_metadata ) { $spacing_rule['selector'] ); } else { - $format = static::ROOT_BLOCK_SELECTOR === $selector ? '%s .%s%s' : '%s.%s%s'; + $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':where(%s .%s) %s' : '%s-%s%s'; $layout_selector = sprintf( $format, $selector, @@ -1339,7 +1337,7 @@ protected function get_layout_styles( $block_metadata ) { if ( ! empty( $class_name ) && - ! empty( $base_style_rules ) + is_array( $base_style_rules ) ) { // Output display mode. This requires special handling as `display` is not exposed in `safe_style_css_filter`. if ( @@ -1930,10 +1928,6 @@ protected static function compute_style_properties( $styles, $settings = array() /** * Returns the style property for the given path. * - * It also converts CSS Custom Property stored as - * "var:preset|color|secondary" to the form - * "--wp--preset--color--secondary". - * * It also converts references to a path to the value * stored at that location, e.g. * { "ref": "style.color.background" } => "#fff". @@ -1991,20 +1985,6 @@ protected static function get_property_value( $styles, $path, $theme_json = null return $value; } - // Convert custom CSS properties. - $prefix = 'var:'; - $prefix_len = strlen( $prefix ); - $token_in = '|'; - $token_out = '--'; - if ( 0 === strncmp( $value, $prefix, $prefix_len ) ) { - $unwrapped_name = str_replace( - $token_in, - $token_out, - substr( $value, $prefix_len ) - ); - $value = "var(--wp--$unwrapped_name)"; - } - return $value; } @@ -2229,8 +2209,8 @@ private static function get_block_nodes( $theme_json, $selectors = array() ) { } $feature_selectors = null; - if ( isset( $selectors[ $name ]['features'] ) ) { - $feature_selectors = $selectors[ $name ]['features']; + if ( isset( $selectors[ $name ]['selectors'] ) ) { + $feature_selectors = $selectors[ $name ]['selectors']; } $variation_selectors = array(); @@ -2247,8 +2227,8 @@ private static function get_block_nodes( $theme_json, $selectors = array() ) { 'name' => $name, 'path' => array( 'styles', 'blocks', $name ), 'selector' => $selector, + 'selectors' => $feature_selectors, 'duotone' => $duotone_selector, - 'features' => $feature_selectors, 'variations' => $variation_selectors, ); @@ -2294,90 +2274,39 @@ public function get_styles_for_block( $block_metadata ) { $selector = $block_metadata['selector']; $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); - /* - * Process style declarations for block support features the current - * block contains selectors for. Values for a feature with a custom - * selector are filtered from the theme.json node before it is - * processed as normal. - */ - $feature_declarations = array(); - - if ( ! empty( $block_metadata['features'] ) ) { - foreach ( $block_metadata['features'] as $feature_name => $feature_selector ) { - if ( ! empty( $node[ $feature_name ] ) ) { - // Create temporary node containing only the feature data - // to leverage existing `compute_style_properties` function. - $feature = array( $feature_name => $node[ $feature_name ] ); - // Generate the feature's declarations only. - $new_feature_declarations = static::compute_style_properties( $feature, $settings, null, $this->theme_json ); - - // Merge new declarations with any that already exist for - // the feature selector. This may occur when multiple block - // support features use the same custom selector. - if ( isset( $feature_declarations[ $feature_selector ] ) ) { - foreach ( $new_feature_declarations as $new_feature_declaration ) { - $feature_declarations[ $feature_selector ][] = $new_feature_declaration; - } - } else { - $feature_declarations[ $feature_selector ] = $new_feature_declarations; - } - - // Remove the feature from the block's node now the - // styles will be included under the feature level selector. - unset( $node[ $feature_name ] ); - } - } - } + $feature_declarations = static::get_feature_declarations_for_node( $block_metadata, $node ); // If there are style variations, generate the declarations for them, including any feature selectors the block may have. $style_variation_declarations = array(); if ( ! empty( $block_metadata['variations'] ) ) { foreach ( $block_metadata['variations'] as $style_variation ) { - $style_variation_node = _wp_array_get( $this->theme_json, $style_variation['path'], array() ); - $style_variation_selector = $style_variation['selector']; - - // If the block has feature selectors, generate the declarations for them within the current style variation. - if ( ! empty( $block_metadata['features'] ) ) { - $clean_style_variation_selector = trim( $style_variation_selector ); - foreach ( $block_metadata['features'] as $feature_name => $feature_selector ) { - if ( empty( $style_variation_node[ $feature_name ] ) ) { - continue; - } - // If feature selector includes block classname, remove it but leave the whitespace in. - $shortened_feature_selector = str_replace( $block_metadata['selector'] . ' ', ' ', $feature_selector ); - // Prepend the variation selector to the feature selector. - $split_feature_selectors = explode( ',', $shortened_feature_selector ); - $feature_selectors = array_map( - static function( $split_feature_selector ) use ( $clean_style_variation_selector ) { - return $clean_style_variation_selector . $split_feature_selector; - }, - $split_feature_selectors - ); - $combined_feature_selectors = implode( ',', $feature_selectors ); - - // Compute declarations for the feature. - $new_feature_declarations = static::compute_style_properties( array( $feature_name => $style_variation_node[ $feature_name ] ), $settings, null, $this->theme_json ); - - /* - * Merge new declarations with any that already exist for - * the feature selector. This may occur when multiple block - * support features use the same custom selector. - */ - if ( isset( $style_variation_declarations[ $combined_feature_selectors ] ) ) { - $style_variation_declarations[ $combined_feature_selectors ] = array_merge( $style_variation_declarations[ $combined_feature_selectors ], $new_feature_declarations ); - } else { - $style_variation_declarations[ $combined_feature_selectors ] = $new_feature_declarations; - } + $style_variation_node = _wp_array_get( $this->theme_json, $style_variation['path'], array() ); + $clean_style_variation_selector = trim( $style_variation['selector'] ); + + // Generate any feature/subfeature style declarations for the current style variation. + $variation_declarations = static::get_feature_declarations_for_node( $block_metadata, $style_variation_node ); + + // Combine selectors with style variation's selector and add to overall style variation declarations. + foreach ( $variation_declarations as $current_selector => $new_declarations ) { + // If current selector includes block classname, remove it but leave the whitespace in. + $shortened_selector = str_replace( $block_metadata['selector'] . ' ', ' ', $current_selector ); + + // Prepend the variation selector to the current selector. + $split_selectors = explode( ',', $shortened_selector ); + $updated_selectors = array_map( + static function( $split_selector ) use ( $clean_style_variation_selector ) { + return $clean_style_variation_selector . $split_selector; + }, + $split_selectors + ); + $combined_selectors = implode( ',', $updated_selectors ); - /* - * Remove the feature from the variation's node now the - * styles will be included under the feature level selector. - */ - unset( $style_variation_node[ $feature_name ] ); - } + // Add the new declarations to the overall results under the modified selector. + $style_variation_declarations[ $combined_selectors ] = $new_declarations; } + // Compute declarations for remaining styles not covered by feature level selectors. - $style_variation_declarations[ $style_variation_selector ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json ); + $style_variation_declarations[ $style_variation['selector'] ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json ); } } @@ -2465,8 +2394,7 @@ function( $pseudo_selector ) use ( $selector ) { // 3. Generate and append the rules that use the duotone selector. if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { - $selector_duotone = static::scope_selector( $block_metadata['selector'], $block_metadata['duotone'] ); - $block_rules .= static::to_ruleset( $selector_duotone, $declarations_duotone ); + $block_rules .= static::to_ruleset( $block_metadata['duotone'], $declarations_duotone ); } // 4. Generate Layout block gap styles. @@ -2554,8 +2482,9 @@ public function get_root_layout_rules( $selector, $block_metadata ) { $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; if ( $has_block_gap_support ) { $block_gap_value = static::get_property_value( $this->theme_json, array( 'styles', 'spacing', 'blockGap' ) ); - $css .= '.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }'; - $css .= ".wp-site-blocks > * + * { margin-block-start: $block_gap_value; }"; + $css .= ":where(.wp-site-blocks) > * { margin-block-start: $block_gap_value; margin-block-end: 0; }"; + $css .= ':where(.wp-site-blocks) > :first-child:first-child { margin-block-start: 0; }'; + $css .= ':where(.wp-site-blocks) > :last-child:last-child { margin-block-end: 0; }'; // For backwards compatibility, ensure the legacy block gap CSS variable is still available. $css .= "$selector { --wp--style--block-gap: $block_gap_value; }"; @@ -2889,8 +2818,15 @@ public static function remove_insecure_properties( $theme_json ) { $valid_block_names = array_keys( static::get_blocks_metadata() ); $valid_element_names = array_keys( static::ELEMENTS ); + $valid_variations = array(); + foreach ( self::get_blocks_metadata() as $block_name => $block_meta ) { + if ( ! isset( $block_meta['styleVariations'] ) ) { + continue; + } + $valid_variations[ $block_name ] = array_keys( $block_meta['styleVariations'] ); + } - $theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names ); + $theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names, $valid_variations ); $blocks_metadata = static::get_blocks_metadata(); $style_nodes = static::get_style_nodes( $theme_json, $blocks_metadata ); @@ -3477,4 +3413,198 @@ public function set_spacing_sizes() { _wp_array_set( $this->theme_json, array( 'settings', 'spacing', 'spacingSizes', 'default' ), $spacing_sizes ); } + + /** + * Returns the selectors metadata for a block. + * + * @param object $block_type The block type. + * @param string $root_selector The block's root selector. + * + * @return object The custom selectors set by the block. + */ + protected static function get_block_selectors( $block_type, $root_selector ) { + if ( ! empty( $block_type->selectors ) ) { + return $block_type->selectors; + } + + $selectors = array( 'root' => $root_selector ); + foreach ( static::BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS as $key => $feature ) { + $feature_selector = wp_get_block_css_selector( $block_type, $key ); + if ( null !== $feature_selector ) { + $selectors[ $feature ] = array( 'root' => $feature_selector ); + } + } + + return $selectors; + } + + /** + * Generates all the element selectors for a block. + * + * @param string $root_selector The block's root CSS selector. + * @return array The block's element selectors. + */ + protected static function get_block_element_selectors( $root_selector ) { + // Assign defaults, then override those that the block sets by itself. + // If the block selector is compounded, will append the element to each + // individual block selector. + $block_selectors = explode( ',', $root_selector ); + $element_selectors = array(); + + foreach ( static::ELEMENTS as $el_name => $el_selector ) { + $element_selector = array(); + foreach ( $block_selectors as $selector ) { + if ( $selector === $el_selector ) { + $element_selector = array( $el_selector ); + break; + } + $element_selector[] = static::append_to_selector( $el_selector, $selector . ' ', 'left' ); + } + $element_selectors[ $el_name ] = implode( ',', $element_selector ); + } + + return $element_selectors; + } + + /** + * Generates style declarations for a node's features e.g. color, border, + * typography etc, that have custom selectors in their related block's + * metadata. + * + * @param object $metadata The related block metadata containing selectors. + * @param object $node A merged theme.json node for block or variation. + * + * @return array The style declarations for the node's features with custom + * selectors. + */ + protected function get_feature_declarations_for_node( $metadata, &$node ) { + $declarations = array(); + + if ( ! isset( $metadata['selectors'] ) ) { + return $declarations; + } + + $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); + + foreach ( $metadata['selectors'] as $feature => $feature_selectors ) { + // Skip if this is the block's root selector or the block doesn't + // have any styles for the feature. + if ( 'root' === $feature || empty( $node[ $feature ] ) ) { + continue; + } + + if ( is_array( $feature_selectors ) ) { + foreach ( $feature_selectors as $subfeature => $subfeature_selector ) { + if ( 'root' === $subfeature || empty( $node[ $feature ][ $subfeature ] ) ) { + continue; + } + + // Create temporary node containing only the subfeature data + // to leverage existing `compute_style_properties` function. + $subfeature_node = array( + $feature => array( + $subfeature => $node[ $feature ][ $subfeature ], + ), + ); + + // Generate style declarations. + $new_declarations = static::compute_style_properties( $subfeature_node, $settings, null, $this->theme_json ); + + // Merge subfeature declarations into feature declarations. + if ( isset( $declarations[ $subfeature_selector ] ) ) { + foreach ( $new_declarations as $new_declaration ) { + $declarations[ $subfeature_selector ][] = $new_declaration; + } + } else { + $declarations[ $subfeature_selector ] = $new_declarations; + } + + // Remove the subfeature from the block's node now its + // styles will be included under its own selector not the + // block's. + unset( $node[ $feature ][ $subfeature ] ); + } + } + + // Now subfeatures have been processed and removed we can process + // feature root selector or simple string selector. + if ( + is_string( $feature_selectors ) || + ( isset( $feature_selectors['root'] ) && $feature_selectors['root'] ) + ) { + $feature_selector = is_string( $feature_selectors ) ? $feature_selectors : $feature_selectors['root']; + + // Create temporary node containing only the feature data + // to leverage existing `compute_style_properties` function. + $feature_node = array( $feature => $node[ $feature ] ); + + // Generate the style declarations. + $new_declarations = static::compute_style_properties( $feature_node, $settings, null, $this->theme_json ); + + // Merge new declarations with any that already exist for + // the feature selector. This may occur when multiple block + // support features use the same custom selector. + if ( isset( $declarations[ $feature_selector ] ) ) { + foreach ( $new_declarations as $new_declaration ) { + $declarations[ $feature_selector ][] = $new_declaration; + } + } else { + $declarations[ $feature_selector ] = $new_declarations; + } + + // Remove the feature from the block's node now its styles + // will be included under its own selector not the block's. + unset( $node[ $feature ] ); + } + } + + return $declarations; + } + + /** + * This is used to convert the internal representation of variables to the CSS representation. + * For example, `var:preset|color|vivid-green-cyan` becomes `var(--wp--preset--color--vivid-green-cyan)`. + * + * @since 6.3.0 + * @param string $value The variable such as var:preset|color|vivid-green-cyan to convert. + * @return string The converted variable. + */ + private static function convert_custom_properties( $value ) { + $prefix = 'var:'; + $prefix_len = strlen( $prefix ); + $token_in = '|'; + $token_out = '--'; + if ( 0 === strpos( $value, $prefix ) ) { + $unwrapped_name = str_replace( + $token_in, + $token_out, + substr( $value, $prefix_len ) + ); + $value = "var(--wp--$unwrapped_name)"; + } + + return $value; + } + + /** + * Given a tree, converts the internal representation of variables to the CSS representation. + * It is recursive and modifies the input in-place. + * + * @since 6.3.0 + * @param array $tree Input to process. + * @return array The modified $tree. + */ + private static function resolve_custom_css_format( $tree ) { + $prefix = 'var:'; + + foreach ( $tree as $key => $data ) { + if ( is_string( $data ) && 0 === strpos( $data, $prefix ) ) { + $tree[ $key ] = self::convert_custom_properties( $data ); + } elseif ( is_array( $data ) ) { + $tree[ $key ] = self::resolve_custom_css_format( $data ); + } + } + + return $tree; + } } diff --git a/lib/client-assets.php b/lib/client-assets.php index 0f6e64c27c0144..9757e4b7ff24a8 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -77,16 +77,8 @@ function gutenberg_override_script( $scripts, $handle, $src, $deps = array(), $v $scripts->add( $handle, $src, $deps, $ver, ( $in_footer ? 1 : null ) ); } - /* - * `WP_Dependencies::set_translations` will fall over on itself if setting - * translations on the `wp-i18n` handle, since it internally adds `wp-i18n` - * as a dependency of itself, exhausting memory. The same applies for the - * polyfill and hooks scripts, which are dependencies _of_ `wp-i18n`. - * - * See: https://core.trac.wordpress.org/ticket/46089 - */ - if ( ! in_array( $handle, array( 'wp-i18n', 'wp-polyfill', 'wp-hooks' ), true ) ) { - $scripts->set_translations( $handle, 'default' ); + if ( in_array( 'wp-i18n', $deps, true ) ) { + $scripts->set_translations( $handle ); } /* @@ -221,6 +213,9 @@ function gutenberg_register_packages_scripts( $scripts ) { case 'wp-edit-site': array_push( $dependencies, 'wp-dom-ready' ); break; + case 'wp-preferences': + array_push( $dependencies, 'wp-preferences-persistence' ); + break; } // Get the path from Gutenberg directory as expected by `gutenberg_url`. @@ -255,7 +250,7 @@ function gutenberg_register_packages_styles( $styles ) { $styles, 'wp-block-editor-content', gutenberg_url( 'build/block-editor/content.css' ), - array(), + array( 'wp-components' ), $version ); $styles->add_data( 'wp-block-editor-content', 'rtl', 'replace' ); @@ -283,7 +278,7 @@ function gutenberg_register_packages_styles( $styles ) { $styles, 'wp-edit-post', gutenberg_url( 'build/edit-post/style.css' ), - array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks', 'wp-block-library' ), + array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks', 'wp-block-library', 'wp-commands' ), $version ); $styles->add_data( 'wp-edit-post', 'rtl', 'replace' ); @@ -385,11 +380,20 @@ function gutenberg_register_packages_styles( $styles ) { ); $styles->add_data( 'wp-list-reusable-block', 'rtl', 'replace' ); + gutenberg_override_style( + $styles, + 'wp-commands', + gutenberg_url( 'build/commands/style.css' ), + array( 'wp-components' ), + $version + ); + $styles->add_data( 'wp-commands', 'rtl', 'replace' ); + gutenberg_override_style( $styles, 'wp-edit-site', gutenberg_url( 'build/edit-site/style.css' ), - array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks' ), + array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks', 'wp-commands' ), $version ); $styles->add_data( 'wp-edit-site', 'rtl', 'replace' ); @@ -428,7 +432,7 @@ function gutenberg_register_packages_styles( $styles ) { array( 'wp-components' ), $version ); - $styles->add_data( 'wp-reusable-block', 'rtl', 'replace' ); + $styles->add_data( 'wp-reusable-blocks', 'rtl', 'replace' ); gutenberg_override_style( $styles, diff --git a/lib/compat/wordpress-6.1/block-editor-settings.php b/lib/compat/wordpress-6.1/block-editor-settings.php deleted file mode 100644 index cafe91e787dc68..00000000000000 --- a/lib/compat/wordpress-6.1/block-editor-settings.php +++ /dev/null @@ -1,177 +0,0 @@ -=' ) && version_compare( $wp_version, '6.0-beta1', '<' ); - $is_wp_6_0 = version_compare( $wp_version, '6.0-beta1', '>=' ); - - // Make sure the styles array exists. - // In some contexts, like the navigation editor, it doesn't. - if ( ! isset( $settings['styles'] ) ) { - $settings['styles'] = array(); - } - - // Remove existing global styles provided by core. - $styles_without_existing_global_styles = array(); - foreach ( $settings['styles'] as $style ) { - if ( - ( $is_wp_5_9 && ! gutenberg_is_global_styles_in_5_9( $style ) ) || // Can be removed when plugin minimum version is 6.0. - ( $is_wp_6_0 && ( ! isset( $style['isGlobalStyles'] ) || ! $style['isGlobalStyles'] ) ) - ) { - $styles_without_existing_global_styles[] = $style; - } - } - - // Recreate global styles. - $new_global_styles = array(); - $presets = array( - array( - 'css' => 'variables', - '__unstableType' => 'presets', - 'isGlobalStyles' => true, - ), - array( - 'css' => 'presets', - '__unstableType' => 'presets', - 'isGlobalStyles' => true, - ), - ); - foreach ( $presets as $preset_style ) { - $actual_css = gutenberg_get_global_stylesheet( array( $preset_style['css'] ) ); - if ( '' !== $actual_css ) { - $preset_style['css'] = $actual_css; - $new_global_styles[] = $preset_style; - } - } - - if ( wp_theme_has_theme_json() ) { - $block_classes = array( - 'css' => 'styles', - '__unstableType' => 'theme', - 'isGlobalStyles' => true, - ); - $actual_css = gutenberg_get_global_stylesheet( array( $block_classes['css'] ) ); - if ( '' !== $actual_css ) { - $block_classes['css'] = $actual_css; - $new_global_styles[] = $block_classes; - } - } else { - // If there is no `theme.json` file, ensure base layout styles are still available. - $block_classes = array( - 'css' => 'base-layout-styles', - '__unstableType' => 'base-layout', - 'isGlobalStyles' => true, - ); - $actual_css = gutenberg_get_global_stylesheet( array( $block_classes['css'] ) ); - if ( '' !== $actual_css ) { - $block_classes['css'] = $actual_css; - $new_global_styles[] = $block_classes; - } - } - - $settings['styles'] = array_merge( $new_global_styles, $styles_without_existing_global_styles ); - } - - // Copied from get_block_editor_settings() at wordpress-develop/block-editor.php. - $settings['__experimentalFeatures'] = gutenberg_get_global_settings(); - - if ( isset( $settings['__experimentalFeatures']['color']['palette'] ) ) { - $colors_by_origin = $settings['__experimentalFeatures']['color']['palette']; - $settings['colors'] = isset( $colors_by_origin['custom'] ) ? - $colors_by_origin['custom'] : ( - isset( $colors_by_origin['theme'] ) ? - $colors_by_origin['theme'] : - $colors_by_origin['default'] - ); - } - - if ( isset( $settings['__experimentalFeatures']['color']['gradients'] ) ) { - $gradients_by_origin = $settings['__experimentalFeatures']['color']['gradients']; - $settings['gradients'] = isset( $gradients_by_origin['custom'] ) ? - $gradients_by_origin['custom'] : ( - isset( $gradients_by_origin['theme'] ) ? - $gradients_by_origin['theme'] : - $gradients_by_origin['default'] - ); - } - - if ( isset( $settings['__experimentalFeatures']['typography']['fontSizes'] ) ) { - $font_sizes_by_origin = $settings['__experimentalFeatures']['typography']['fontSizes']; - $settings['fontSizes'] = isset( $font_sizes_by_origin['custom'] ) ? - $font_sizes_by_origin['custom'] : ( - isset( $font_sizes_by_origin['theme'] ) ? - $font_sizes_by_origin['theme'] : - $font_sizes_by_origin['default'] - ); - } - - if ( isset( $settings['__experimentalFeatures']['color']['custom'] ) ) { - $settings['disableCustomColors'] = ! $settings['__experimentalFeatures']['color']['custom']; - unset( $settings['__experimentalFeatures']['color']['custom'] ); - } - if ( isset( $settings['__experimentalFeatures']['color']['customGradient'] ) ) { - $settings['disableCustomGradients'] = ! $settings['__experimentalFeatures']['color']['customGradient']; - unset( $settings['__experimentalFeatures']['color']['customGradient'] ); - } - if ( isset( $settings['__experimentalFeatures']['typography']['customFontSize'] ) ) { - $settings['disableCustomFontSizes'] = ! $settings['__experimentalFeatures']['typography']['customFontSize']; - unset( $settings['__experimentalFeatures']['typography']['customFontSize'] ); - } - if ( isset( $settings['__experimentalFeatures']['typography']['lineHeight'] ) ) { - $settings['enableCustomLineHeight'] = $settings['__experimentalFeatures']['typography']['lineHeight']; - unset( $settings['__experimentalFeatures']['typography']['lineHeight'] ); - } - if ( isset( $settings['__experimentalFeatures']['spacing']['units'] ) ) { - $settings['enableCustomUnits'] = $settings['__experimentalFeatures']['spacing']['units']; - unset( $settings['__experimentalFeatures']['spacing']['units'] ); - } - if ( isset( $settings['__experimentalFeatures']['spacing']['padding'] ) ) { - $settings['enableCustomSpacing'] = $settings['__experimentalFeatures']['spacing']['padding']; - unset( $settings['__experimentalFeatures']['spacing']['padding'] ); - } - if ( isset( $settings['__experimentalFeatures']['spacing']['customSpacingSize'] ) ) { - $settings['disableCustomSpacingSizes'] = ! $settings['__experimentalFeatures']['spacing']['customSpacingSize']; - unset( $settings['__experimentalFeatures']['spacing']['customSpacingSize'] ); - } - - if ( isset( $settings['__experimentalFeatures']['spacing']['spacingSizes'] ) ) { - $spacing_sizes_by_origin = $settings['__experimentalFeatures']['spacing']['spacingSizes']; - $settings['spacingSizes'] = isset( $spacing_sizes_by_origin['custom'] ) ? - $spacing_sizes_by_origin['custom'] : ( - isset( $spacing_sizes_by_origin['theme'] ) ? - $spacing_sizes_by_origin['theme'] : - $spacing_sizes_by_origin['default'] - ); - } - - $settings['localAutosaveInterval'] = 15; - $settings['disableLayoutStyles'] = current_theme_supports( 'disable-layout-styles' ); - - return $settings; -} - -add_filter( 'block_editor_settings_all', 'gutenberg_get_block_editor_settings', PHP_INT_MAX ); diff --git a/lib/compat/wordpress-6.1/block-template-utils.php b/lib/compat/wordpress-6.1/block-template-utils.php deleted file mode 100644 index 2005af46be5a20..00000000000000 --- a/lib/compat/wordpress-6.1/block-template-utils.php +++ /dev/null @@ -1,626 +0,0 @@ - _x( 'Single', 'Template name', 'gutenberg' ), - 'description' => __( 'The default template for displaying any single post or attachment.', 'gutenberg' ), - ); - } - if ( isset( $default_template_types['category'] ) ) { - $default_template_types['category'] = array( - 'title' => _x( 'Category', 'Template name', 'gutenberg' ), - 'description' => __( 'Displays latest posts from a single post category.', 'gutenberg' ), - ); - } - return $default_template_types; -} -add_filter( 'default_template_types', 'gutenberg_get_default_block_template_types', 10 ); - - -/** - * Retrieves a list of unified template objects based on a query. - * - * @param array $query { - * Optional. Arguments to retrieve templates. - * - * @type array $slug__in List of slugs to include. - * @type int $wp_id Post ID of customized template. - * @type string $area A 'wp_template_part_area' taxonomy value to filter by (for wp_template_part template type only). - * @type string $post_type Post type to get the templates for. - * } - * @param array $template_type wp_template or wp_template_part. - * - * @return array Templates. - */ -function gutenberg_get_block_templates( $query = array(), $template_type = 'wp_template' ) { - /** - * Filters the block templates array before the query takes place. - * - * Return a non-null value to bypass the WordPress queries. - * - * @since 10.8 - * - * @param Gutenberg_Block_Template[]|null $block_templates Return an array of block templates to short-circuit the default query, - * or null to allow WP to run it's normal queries. - * @param array $query { - * Optional. Arguments to retrieve templates. - * - * @type array $slug__in List of slugs to include. - * @type int $wp_id Post ID of customized template. - * @type string $post_type Post type to get the templates for. - * } - * @param array $template_type wp_template or wp_template_part. - */ - $templates = apply_filters( 'pre_get_block_templates', null, $query, $template_type ); - if ( ! is_null( $templates ) ) { - return $templates; - } - - $post_type = isset( $query['post_type'] ) ? $query['post_type'] : ''; - $wp_query_args = array( - 'post_status' => array( 'auto-draft', 'draft', 'publish' ), - 'post_type' => $template_type, - 'posts_per_page' => -1, - 'no_found_rows' => true, - 'lazy_load_term_meta' => false, // Do not lazy load term meta, as template post types only have one term. - 'tax_query' => array( - array( - 'taxonomy' => 'wp_theme', - 'field' => 'name', - 'terms' => get_stylesheet(), - ), - ), - ); - - if ( 'wp_template_part' === $template_type && isset( $query['area'] ) ) { - $wp_query_args['tax_query'][] = array( - 'taxonomy' => 'wp_template_part_area', - 'field' => 'name', - 'terms' => $query['area'], - ); - $wp_query_args['tax_query']['relation'] = 'AND'; - } - - if ( isset( $query['slug__in'] ) ) { - $wp_query_args['post_name__in'] = $query['slug__in']; - } - - // This is only needed for the regular templates/template parts CPT listing and editor. - if ( isset( $query['wp_id'] ) ) { - $wp_query_args['p'] = $query['wp_id']; - } else { - $wp_query_args['post_status'] = 'publish'; - } - - $template_query = new WP_Query( $wp_query_args ); - $query_result = array(); - foreach ( $template_query->posts as $post ) { - $template = gutenberg_build_block_template_result_from_post( $post ); - if ( is_wp_error( $template ) ) { - continue; - } - - if ( $post_type && ! $template->is_custom ) { - continue; - } - - if ( $post_type && - isset( $template->post_types ) && - ! in_array( $post_type, $template->post_types, true ) - ) { - continue; - } - - $query_result[] = $template; - } - if ( ! isset( $query['wp_id'] ) ) { - $template_files = _get_block_templates_files( $template_type ); - foreach ( $template_files as $template_file ) { - $template = _build_block_template_result_from_file( $template_file, $template_type ); - - if ( $post_type && ! $template->is_custom ) { - continue; - } - - if ( $post_type && - isset( $template->post_types ) && - ! in_array( $post_type, $template->post_types, true ) - ) { - continue; - } - - $is_not_custom = false === array_search( - get_stylesheet() . '//' . $template_file['slug'], - array_column( $query_result, 'id' ), - true - ); - $fits_slug_query = - ! isset( $query['slug__in'] ) || in_array( $template_file['slug'], $query['slug__in'], true ); - $fits_area_query = - ! isset( $query['area'] ) || $template_file['area'] === $query['area']; - $should_include = $is_not_custom && $fits_slug_query && $fits_area_query; - if ( $should_include ) { - $query_result[] = $template; - } - } - } - /** - * Filters the array of queried block templates array after they've been fetched. - * - * @since 10.8 - * - * @param Gutenberg_Block_Template[] $query_result Array of found block templates. - * @param array $query { - * Optional. Arguments to retrieve templates. - * - * @type array $slug__in List of slugs to include. - * @type int $wp_id Post ID of customized template. - * } - * @param array $template_type wp_template or wp_template_part. - */ - return apply_filters( 'get_block_templates', $query_result, $query, $template_type ); -} - -/** - * Retrieves a single unified template object using its id. - * - * @param string $id Template unique identifier (example: theme_slug//template_slug). - * @param array $template_type wp_template or wp_template_part. - * - * @return Gutenberg_Block_Template|null Template. - */ -function gutenberg_get_block_template( $id, $template_type = 'wp_template' ) { - /** - * Filters the block template object before the query takes place. - * - * Return a non-null value to bypass the WordPress queries. - * - * @since 10.8 - * - * @param Gutenberg_Block_Template|null $block_template Return block template object to short-circuit the default query, - * or null to allow WP to run it's normal queries. - * @param string $id Template unique identifier (example: theme_slug//template_slug). - * @param array $template_type wp_template or wp_template_part. - */ - $block_template = apply_filters( 'pre_get_block_template', null, $id, $template_type ); - if ( ! is_null( $block_template ) ) { - return $block_template; - } - - $parts = explode( '//', $id, 2 ); - if ( count( $parts ) < 2 ) { - return null; - } - list( $theme, $slug ) = $parts; - $wp_query_args = array( - 'post_name__in' => array( $slug ), - 'post_type' => $template_type, - 'post_status' => array( 'auto-draft', 'draft', 'publish', 'trash' ), - 'posts_per_page' => 1, - 'no_found_rows' => true, - 'tax_query' => array( - array( - 'taxonomy' => 'wp_theme', - 'field' => 'name', - 'terms' => $theme, - ), - ), - ); - $template_query = new WP_Query( $wp_query_args ); - $posts = $template_query->posts; - - if ( count( $posts ) > 0 ) { - $template = gutenberg_build_block_template_result_from_post( $posts[0] ); - - if ( ! is_wp_error( $template ) ) { - return $template; - } - } - - $block_template = get_block_file_template( $id, $template_type ); - - /** - * Filters the queried block template object after it's been fetched. - * - * @since 10.8 - * - * @param Gutenberg_Block_Template|null $block_template The found block template, or null if there isn't one. - * @param string $id Template unique identifier (example: theme_slug//template_slug). - * @param array $template_type wp_template or wp_template_part. - */ - return apply_filters( 'get_block_template', $block_template, $id, $template_type ); -} - -/** - * Builds the title and description of a post-specific template based on the underlying referenced post. - * Mutates the underlying template object. - * - * @since 6.1.0 - * @access private - * @internal - * - * @param string $post_type Post type e.g.: page, post, product. - * @param string $slug Slug of the post e.g.: a-story-about-shoes. - * @param WP_Block_Template $template Template to mutate adding the description and title computed. - * @return boolean Returns true if the referenced post was found and false otherwise. - */ -function _gutenberg_build_title_and_description_for_single_post_type_block_template( $post_type, $slug, WP_Block_Template $template ) { - $post_type_object = get_post_type_object( $post_type ); - - $default_args = array( - 'post_type' => $post_type, - 'post_status' => 'publish', - 'posts_per_page' => 1, - 'update_post_meta_cache' => false, - 'update_post_term_cache' => false, - 'ignore_sticky_posts' => true, - 'no_found_rows' => true, - ); - - $args = array( - 'name' => $slug, - ); - $args = wp_parse_args( $args, $default_args ); - - $posts_query = new WP_Query( $args ); - - if ( empty( $posts_query->posts ) ) { - $template->title = sprintf( - /* translators: Custom template title in the Site Editor referencing a post that was not found. 1: Post type singular name, 2: Post type slug. */ - __( 'Not found: %1$s (%2$s)', 'gutenberg' ), - $post_type_object->labels->singular_name, - $slug - ); - - return false; - } - - $post_title = $posts_query->posts[0]->post_title; - - $template->title = sprintf( - /* translators: Custom template title in the Site Editor. 1: Post type singular name, 2: Post title. */ - __( '%1$s: %2$s', 'gutenberg' ), - $post_type_object->labels->singular_name, - $post_title - ); - - $template->description = sprintf( - /* translators: Custom template description in the Site Editor. %s: Post title. */ - __( 'Template for %s', 'gutenberg' ), - $post_title - ); - - $args = array( - 'title' => $post_title, - ); - $args = wp_parse_args( $args, $default_args ); - - $posts_with_same_title_query = new WP_Query( $args ); - - if ( count( $posts_with_same_title_query->posts ) > 1 ) { - $template->title = sprintf( - /* translators: Custom template title in the Site Editor. 1: Template title, 2: Post type slug. */ - __( '%1$s (%2$s)', 'gutenberg' ), - $template->title, - $slug - ); - } - - return true; -} - -/** - * Builds the title and description of a taxonomy-specific template based on the underlying entity referenced. - * Mutates the underlying template object. - * - * @access private - * @internal - * - * @param string $taxonomy Identifier of the taxonomy, e.g.: category. - * @param string $slug Slug of the term, e.g.: shoes. - * @param WP_Block_Template $template Template to mutate adding the description and title computed. - * - * @return boolean True if the term referenced was found and false otherwise. - */ -function _gutenberg_build_title_and_description_for_taxonomy_block_template( $taxonomy, $slug, WP_Block_Template $template ) { - $taxonomy_object = get_taxonomy( $taxonomy ); - - $default_args = array( - 'taxonomy' => $taxonomy, - 'hide_empty' => false, - 'update_term_meta_cache' => false, - ); - - $term_query = new WP_Term_Query(); - - $args = array( - 'number' => 1, - 'slug' => $slug, - ); - $args = wp_parse_args( $args, $default_args ); - - $terms_query = $term_query->query( $args ); - - if ( empty( $terms_query ) ) { - $template->title = sprintf( - /* translators: Custom template title in the Site Editor, referencing a taxonomy term that was not found. 1: Taxonomy singular name, 2: Term slug. */ - __( 'Not found: %1$s (%2$s)', 'gutenberg' ), - $taxonomy_object->labels->singular_name, - $slug - ); - return false; - } - - $term_title = $terms_query[0]->name; - - $template->title = sprintf( - /* translators: Custom template title in the Site Editor. 1: Taxonomy singular name, 2: Term title. */ - __( '%1$s: %2$s', 'gutenberg' ), - $taxonomy_object->labels->singular_name, - $term_title - ); - - $template->description = sprintf( - /* translators: Custom template description in the Site Editor. %s: Term title. */ - __( 'Template for %s', 'gutenberg' ), - $term_title - ); - - $term_query = new WP_Term_Query(); - - $args = array( - 'number' => 2, - 'name' => $term_title, - ); - $args = wp_parse_args( $args, $default_args ); - - $terms_with_same_title_query = $term_query->query( $args ); - - if ( count( $terms_with_same_title_query ) > 1 ) { - $template->title = sprintf( - /* translators: Custom template title in the Site Editor. 1: Template title, 2: Term slug. */ - __( '%1$s (%2$s)', 'gutenberg' ), - $template->title, - $slug - ); - } - - return true; -} - -/** - * Build a unified template object based a post Object. - * - * @param WP_Post $post Template post. - * - * @return Gutenberg_Block_Template|WP_Error Template. - */ -function gutenberg_build_block_template_result_from_post( $post ) { - $default_template_types = get_default_block_template_types(); - $terms = get_the_terms( $post, 'wp_theme' ); - - if ( is_wp_error( $terms ) ) { - return $terms; - } - - if ( ! $terms ) { - return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.', 'gutenberg' ) ); - } - - $origin = get_post_meta( $post->ID, 'origin', true ); - $is_wp_suggestion = get_post_meta( $post->ID, 'is_wp_suggestion', true ); - - $theme = $terms[0]->name; - $template_file = _get_block_template_file( $post->post_type, $post->post_name ); - $has_theme_file = get_stylesheet() === $theme && null !== $template_file; - - $template = new WP_Block_Template(); - $template->wp_id = $post->ID; - $template->id = $theme . '//' . $post->post_name; - $template->theme = $theme; - $template->content = $post->post_content; - $template->slug = $post->post_name; - $template->source = 'custom'; - $template->origin = ! empty( $origin ) ? $origin : null; - $template->type = $post->post_type; - $template->description = $post->post_excerpt; - $template->title = $post->post_title; - $template->status = $post->post_status; - $template->has_theme_file = $has_theme_file; - $template->is_custom = empty( $is_wp_suggestion ); - $template->author = $post->post_author; - - // We keep this check for existent templates that are part of the template hierarchy. - if ( 'wp_template' === $post->post_type && isset( $default_template_types[ $template->slug ] ) ) { - $template->is_custom = false; - } - - if ( 'wp_template' === $post->post_type && $has_theme_file && isset( $template_file['postTypes'] ) ) { - $template->post_types = $template_file['postTypes']; - } - - if ( 'wp_template_part' === $post->post_type ) { - $type_terms = get_the_terms( $post, 'wp_template_part_area' ); - if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) { - $template->area = $type_terms[0]->name; - } - } - // If it is a block template without description and without title or with title equal to the slug. - if ( 'wp_template' === $post->post_type && empty( $template->description ) && ( empty( $template->title ) || $template->title === $template->slug ) ) { - $matches = array(); - // If it is a block template for a single author, page, post, tag, category, custom post type or custom taxonomy. - if ( preg_match( '/(author|page|single|tag|category|taxonomy)-(.+)/', $template->slug, $matches ) ) { - $type = $matches[1]; - $slug_remaining = $matches[2]; - switch ( $type ) { - case 'author': - $nice_name = $slug_remaining; - $users = get_users( - array( - 'capability' => 'edit_posts', - 'search' => $nice_name, - 'search_columns' => array( 'user_nicename' ), - 'fields' => 'display_name', - ) - ); - - if ( empty( $users ) ) { - $template->title = sprintf( - // translators: Represents the title of a user's custom template in the Site Editor referencing a deleted author, where %s is the author's nicename, e.g. "Deleted author: jane-doe". - __( 'Deleted author: %s', 'gutenberg' ), - $nice_name - ); - } else { - $author_name = $users[0]; - - $template->title = sprintf( - // translators: Represents the title of a user's custom template in the Site Editor, where %s is the author's name, e.g. "Author: Jane Doe". - __( 'Author: %s', 'gutenberg' ), - $author_name - ); - $template->description = sprintf( - // translators: Represents the description of a user's custom template in the Site Editor, e.g. "Template for Author: Jane Doe". - __( 'Template for %1$s', 'gutenberg' ), - $author_name - ); - - $users_with_same_name = get_users( - array( - 'capability' => 'edit_posts', - 'search' => $author_name, - 'search_columns' => array( 'display_name' ), - 'fields' => 'display_name', - ) - ); - if ( count( $users_with_same_name ) > 1 ) { - $template->title = sprintf( - // translators: Represents the title of a user's custom template in the Site Editor, where %1$s is the template title of an author template and %2$s is the nicename of the author, e.g. "Author: Jane Doe (jane-doe)". - __( '%1$s (%2$s)', 'gutenberg' ), - $template->title, - $nice_name - ); - } - } - break; - case 'page': - _gutenberg_build_title_and_description_for_single_post_type_block_template( 'page', $slug_remaining, $template ); - break; - case 'single': - $post_types = get_post_types(); - foreach ( $post_types as $post_type ) { - $post_type_length = strlen( $post_type ) + 1; - // If $slug_remaining starts with $post_type followed by a hyphen. - if ( 0 === strncmp( $slug_remaining, $post_type . '-', $post_type_length ) ) { - $slug = substr( $slug_remaining, $post_type_length, strlen( $slug_remaining ) ); - $found = _gutenberg_build_title_and_description_for_single_post_type_block_template( $post_type, $slug, $template ); - if ( $found ) { - break; - } - } - } - break; - case 'tag': - _gutenberg_build_title_and_description_for_taxonomy_block_template( 'post_tag', $slug_remaining, $template ); - break; - case 'category': - _gutenberg_build_title_and_description_for_taxonomy_block_template( 'category', $slug_remaining, $template ); - break; - case 'taxonomy': - $taxonomies = get_taxonomies(); - foreach ( $taxonomies as $taxonomy ) { - $taxonomy_length = strlen( $taxonomy ) + 1; - // If $slug_remaining starts with $taxonomy followed by a hyphen. - if ( 0 === strncmp( $slug_remaining, $taxonomy . '-', $taxonomy_length ) ) { - $slug = substr( $slug_remaining, $taxonomy_length, strlen( $slug_remaining ) ); - $found = _gutenberg_build_title_and_description_for_taxonomy_block_template( $taxonomy, $slug, $template ); - if ( $found ) { - break; - } - } - } - break; - } - } - } - return $template; -} - -if ( ! function_exists( 'get_template_hierarchy' ) ) { - /** - * Helper function to get the Template Hierarchy for a given slug. - * We need to Handle special cases here like `front-page`, `singular` and `archive` templates. - * - * Noting that we always add `index` as the last fallback template. - * - * @param string $slug The template slug to be created. - * @param boolean $is_custom Indicates if a template is custom or part of the template hierarchy. - * @param string $template_prefix The template prefix for the created template. This is used to extract the main template type ex. in `taxonomy-books` we extract the `taxonomy`. - * - * @return array The template hierarchy. - */ - function get_template_hierarchy( $slug, $is_custom = false, $template_prefix = '' ) { - if ( 'index' === $slug ) { - return array( 'index' ); - } - if ( $is_custom ) { - return array( 'page', 'singular', 'index' ); - } - if ( 'front-page' === $slug ) { - return array( 'front-page', 'home', 'index' ); - } - $template_hierarchy = array( $slug ); - // Most default templates don't have `$template_prefix` assigned. - if ( $template_prefix ) { - list($type) = explode( '-', $template_prefix ); - // We need these checks because we always add the `$slug` above. - if ( ! in_array( $template_prefix, array( $slug, $type ), true ) ) { - $template_hierarchy[] = $template_prefix; - } - if ( $slug !== $type ) { - $template_hierarchy[] = $type; - } - } - // Handle `archive` template. - if ( - str_starts_with( $slug, 'author' ) || - str_starts_with( $slug, 'taxonomy' ) || - str_starts_with( $slug, 'category' ) || - str_starts_with( $slug, 'tag' ) || - 'date' === $slug - ) { - $template_hierarchy[] = 'archive'; - } - // Handle `single` template. - if ( 'attachment' === $slug ) { - $template_hierarchy[] = 'single'; - } - // Handle `singular` template. - if ( - str_starts_with( $slug, 'single' ) || - str_starts_with( $slug, 'page' ) || - 'attachment' === $slug - ) { - $template_hierarchy[] = 'singular'; - } - $template_hierarchy[] = 'index'; - return $template_hierarchy; - } -} diff --git a/lib/compat/wordpress-6.1/blocks.php b/lib/compat/wordpress-6.1/blocks.php deleted file mode 100644 index 8ee0acba6b6cfe..00000000000000 --- a/lib/compat/wordpress-6.1/blocks.php +++ /dev/null @@ -1,183 +0,0 @@ -= 6.1. - * - * @param string[] $attrs Array of allowed CSS attributes. - * @return string[] CSS attributes. - */ -function gutenberg_safe_style_attrs_6_1( $attrs ) { - $attrs[] = 'flex-wrap'; - $attrs[] = 'gap'; - $attrs[] = 'margin-block-start'; - $attrs[] = 'margin-block-end'; - $attrs[] = 'margin-inline-start'; - $attrs[] = 'margin-inline-end'; - - return $attrs; -} -add_filter( 'safe_style_css', 'gutenberg_safe_style_attrs_6_1' ); - -/** - * Update allowed CSS values to match WordPress 6.1. - * - * Note: This should be removed when the minimum required WP version is >= 6.1. - * - * The logic in this function follows that provided in: https://core.trac.wordpress.org/ticket/55966. - * - * @param boolean $allow_css Whether or not the current test string is allowed. - * @param string $css_test_string The CSS string to be tested. - * @return boolean - */ -function gutenberg_safecss_filter_attr_allow_css_6_1( $allow_css, $css_test_string ) { - if ( false === $allow_css ) { - /* - * Allow CSS functions like var(), calc(), etc. by removing them from the test string. - * Nested functions and parentheses are also removed, so long as the parentheses are balanced. - */ - $css_test_string = preg_replace( - '/\b(?:var|calc|min|max|minmax|clamp)(\((?:[^()]|(?1))*\))/', - '', - $css_test_string - ); - - // Check for any CSS containing \ ( & } = or comments, - // except for url(), calc(), or var() usage checked above. - $allow_css = ! preg_match( '%[\\\(&=}]|/\*%', $css_test_string ); - } - return $allow_css; -} -add_filter( 'safecss_filter_attr_allow_css', 'gutenberg_safecss_filter_attr_allow_css_6_1', 10, 2 ); - -/** - * Registers view scripts for core blocks if handling is missing in WordPress core. - * - * @since 6.1.0 - * - * @param array $settings Array of determined settings for registering a block type. - * @param array $metadata Metadata provided for registering a block type. - * - * @return array Array of settings for registering a block type. - */ -function gutenberg_block_type_metadata_view_script( $settings, $metadata ) { - if ( - ! isset( $metadata['viewScript'] ) || - ! empty( $settings['view_script'] ) || - ! isset( $metadata['file'] ) || - ! str_starts_with( $metadata['file'], wp_normalize_path( gutenberg_dir_path() ) ) - ) { - return $settings; - } - - $view_script_path = wp_normalize_path( realpath( dirname( $metadata['file'] ) . '/' . remove_block_asset_path_prefix( $metadata['viewScript'] ) ) ); - - if ( file_exists( $view_script_path ) ) { - $view_script_id = str_replace( array( '.min.js', '.js' ), '', basename( remove_block_asset_path_prefix( $metadata['viewScript'] ) ) ); - $view_script_handle = str_replace( 'core/', 'wp-block-', $metadata['name'] ) . '-' . $view_script_id; - wp_deregister_script( $view_script_handle ); - - // Replace suffix and extension with `.asset.php` to find the generated dependencies file. - $view_asset_file = substr( $view_script_path, 0, -( strlen( '.js' ) ) ) . '.asset.php'; - $view_asset = file_exists( $view_asset_file ) ? require $view_asset_file : null; - $view_script_dependencies = isset( $view_asset['dependencies'] ) ? $view_asset['dependencies'] : array(); - $view_script_version = isset( $view_asset['version'] ) ? $view_asset['version'] : false; - $result = wp_register_script( - $view_script_handle, - gutenberg_url( str_replace( wp_normalize_path( gutenberg_dir_path() ), '', $view_script_path ) ), - $view_script_dependencies, - $view_script_version - ); - if ( $result ) { - $settings['view_script'] = $view_script_handle; - - if ( ! empty( $metadata['textdomain'] ) && in_array( 'wp-i18n', $view_script_dependencies, true ) ) { - wp_set_script_translations( $view_script_handle, $metadata['textdomain'] ); - } - } - } - return $settings; -} -add_filter( 'block_type_metadata_settings', 'gutenberg_block_type_metadata_view_script', 10, 2 ); - -/** - * Allow multiple view scripts per block. - * - * Filters the metadata provided for registering a block type. - * - * @since 6.1.0 - * - * @param array $metadata Metadata for registering a block type. - * - * @return array - */ -function gutenberg_block_type_metadata_multiple_view_scripts( $metadata ) { - - // Early return if viewScript is empty, or not an array. - if ( ! isset( $metadata['viewScript'] ) || ! is_array( $metadata['viewScript'] ) ) { - return $metadata; - } - - // Register all viewScript items. - foreach ( $metadata['viewScript'] as $view_script ) { - $item_metadata = $metadata; - $item_metadata['viewScript'] = $view_script; - gutenberg_block_type_metadata_view_script( array(), $item_metadata ); - } - - // Proceed with the default behavior. - $metadata['viewScript'] = $metadata['viewScript'][0]; - return $metadata; -} -add_filter( 'block_type_metadata', 'gutenberg_block_type_metadata_multiple_view_scripts' ); - -/** - * Register render template for core blocks if handling is missing in WordPress core. - * - * @since 6.1.0 - * - * @param array $settings Array of determined settings for registering a block type. - * @param array $metadata Metadata provided for registering a block type. - * - * @return array Array of settings for registering a block type. - */ -function gutenberg_block_type_metadata_render_template( $settings, $metadata ) { - if ( empty( $metadata['render'] ) || isset( $settings['render_callback'] ) ) { - return $settings; - } - - $template_path = wp_normalize_path( - realpath( - dirname( $metadata['file'] ) . '/' . - remove_block_asset_path_prefix( $metadata['render'] ) - ) - ); - - // Bail if the file does not exist. - if ( ! file_exists( $template_path ) ) { - return $settings; - } - /** - * Renders the block on the server. - * - * @param array $attributes Block attributes. - * @param string $content Block default content. - * @param WP_Block $block Block instance. - * - * @return string Returns the block content. - */ - $settings['render_callback'] = function( $attributes, $content, $block ) use ( $template_path ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - ob_start(); - require $template_path; - return ob_get_clean(); - }; - - return $settings; -} -add_filter( 'block_type_metadata_settings', 'gutenberg_block_type_metadata_render_template', 10, 2 ); diff --git a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller-6-1.php b/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller-6-1.php deleted file mode 100644 index b40f3aa2497f16..00000000000000 --- a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller-6-1.php +++ /dev/null @@ -1,133 +0,0 @@ -get_fields_for_response( $request ); - $keys = array( - 'name' => 'name', - 'title' => 'title', - 'description' => 'description', - 'viewportWidth' => 'viewport_width', - 'blockTypes' => 'block_types', - 'postTypes' => 'post_types', - 'categories' => 'categories', - 'keywords' => 'keywords', - 'content' => 'content', - 'inserter' => 'inserter', - ); - $data = array(); - foreach ( $keys as $item_key => $rest_key ) { - if ( isset( $item[ $item_key ] ) && rest_is_field_included( $rest_key, $fields ) ) { - $data[ $rest_key ] = $item[ $item_key ]; - } - } - - $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; - $data = $this->add_additional_fields_to_object( $data, $request ); - $data = $this->filter_response_by_context( $data, $context ); - return rest_ensure_response( $data ); - } - - /** - * Retrieves the block pattern schema, conforming to JSON Schema. - * - * @since 6.0.0 - * @since 6.1.0 Added `post_types` property. - * - * @return array Item schema data. - */ - public function get_item_schema() { - $schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => 'block-pattern', - 'type' => 'object', - 'properties' => array( - 'name' => array( - 'description' => __( 'The pattern name.', 'gutenberg' ), - 'type' => 'string', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'title' => array( - 'description' => __( 'The pattern title, in human readable format.', 'gutenberg' ), - 'type' => 'string', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'description' => array( - 'description' => __( 'The pattern detailed description.', 'gutenberg' ), - 'type' => 'string', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'viewport_width' => array( - 'description' => __( 'The pattern viewport width for inserter preview.', 'gutenberg' ), - 'type' => 'number', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'block_types' => array( - 'description' => __( 'Block types that the pattern is intended to be used with.', 'gutenberg' ), - 'type' => 'array', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'post_types' => array( - 'description' => __( 'An array of post types that the pattern is restricted to be used with.', 'gutenberg' ), - 'type' => 'array', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'categories' => array( - 'description' => __( 'The pattern category slugs.', 'gutenberg' ), - 'type' => 'array', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'keywords' => array( - 'description' => __( 'The pattern keywords.', 'gutenberg' ), - 'type' => 'array', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'content' => array( - 'description' => __( 'The pattern content.', 'gutenberg' ), - 'type' => 'string', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'inserter' => array( - 'description' => __( 'Determines whether the pattern is visible in inserter.', 'gutenberg' ), - 'type' => 'boolean', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - ), - ); - - return $this->add_additional_fields_schema( $schema ); - } -} diff --git a/lib/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php b/lib/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php deleted file mode 100644 index f0416e0a50e96f..00000000000000 --- a/lib/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php +++ /dev/null @@ -1,303 +0,0 @@ -namespace, - '/' . $this->rest_base . '/lookup', - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_template_fallback' ), - 'permission_callback' => array( $this, 'get_item_permissions_check' ), - 'args' => array( - 'slug' => array( - 'description' => __( 'The slug of the template to get the fallback for', 'gutenberg' ), - 'type' => 'string', - 'required' => true, - ), - 'is_custom' => array( - 'description' => __( 'Indicates if a template is custom or part of the template hierarchy', 'gutenberg' ), - 'type' => 'boolean', - ), - 'template_prefix' => array( - 'description' => __( 'The template prefix for the created template. This is used to extract the main template type ex. in `taxonomy-books` we extract the `taxonomy`', 'gutenberg' ), - 'type' => 'string', - ), - ), - ), - ) - ); - parent::register_routes(); - } - - /** - * Returns the fallback template for a given slug. - * - * @param WP_REST_Request $request The request instance. - * - * @return WP_REST_Response|WP_Error - */ - public function get_template_fallback( $request ) { - $hierarchy = gutenberg_get_template_hierarchy( $request['slug'], $request['is_custom'], $request['template_prefix'] ); - $fallback_template = resolve_block_template( $request['slug'], $hierarchy, '' ); - $response = $this->prepare_item_for_response( $fallback_template, $request ); - return rest_ensure_response( $response ); - } - - /** - * Returns a list of templates. - * - * @param WP_REST_Request $request The request instance. - * - * @return WP_REST_Response - */ - public function get_items( $request ) { - $query = array(); - if ( isset( $request['wp_id'] ) ) { - $query['wp_id'] = $request['wp_id']; - } - if ( isset( $request['area'] ) ) { - $query['area'] = $request['area']; - } - if ( isset( $request['post_type'] ) ) { - $query['post_type'] = $request['post_type']; - } - - $templates = array(); - foreach ( gutenberg_get_block_templates( $query, $this->post_type ) as $template ) { - $data = $this->prepare_item_for_response( $template, $request ); - $templates[] = $this->prepare_response_for_collection( $data ); - } - - return rest_ensure_response( $templates ); - } - - /** - * Returns the given template - * - * @param WP_REST_Request $request The request instance. - * - * @return WP_REST_Response|WP_Error - */ - public function get_item( $request ) { - if ( isset( $request['source'] ) && 'theme' === $request['source'] ) { - $template = get_block_file_template( $request['id'], $this->post_type ); - } else { - $template = gutenberg_get_block_template( $request['id'], $this->post_type ); - } - - if ( ! $template ) { - return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); - } - - return $this->prepare_item_for_response( $template, $request ); - } - - /** - * Creates a single template. - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. - */ - public function create_item( $request ) { - $changes = $this->prepare_item_for_database( $request ); - if ( is_wp_error( $changes ) ) { - return $changes; - } - - $changes->post_name = $request['slug']; - $result = wp_insert_post( wp_slash( (array) $changes ), true ); - if ( is_wp_error( $result ) ) { - return $result; - } - - $posts = gutenberg_get_block_templates( array( 'wp_id' => $result ), $this->post_type ); - if ( ! count( $posts ) ) { - return new WP_Error( 'rest_template_insert_error', __( 'No templates exist with that id.', 'gutenberg' ) ); - } - $id = $posts[0]->id; - $template = gutenberg_get_block_template( $id, $this->post_type ); - - $fields_update = $this->update_additional_fields_for_object( $template, $request ); - if ( is_wp_error( $fields_update ) ) { - return $fields_update; - } - - return $this->prepare_item_for_response( - gutenberg_get_block_template( $id, $this->post_type ), - $request - ); - } - - /** - * Updates a single template. - * - * @since 5.8.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. - */ - public function update_item( $request ) { - $template = gutenberg_get_block_template( $request['id'], $this->post_type ); - if ( ! $template ) { - return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); - } - - $post_before = get_post( $template->wp_id ); - - if ( isset( $request['source'] ) && 'theme' === $request['source'] ) { - wp_delete_post( $template->wp_id, true ); - $request->set_param( 'context', 'edit' ); - - $template = gutenberg_get_block_template( $request['id'], $this->post_type ); - $response = $this->prepare_item_for_response( $template, $request ); - - return rest_ensure_response( $response ); - } - - $changes = $this->prepare_item_for_database( $request ); - - if ( is_wp_error( $changes ) ) { - return $changes; - } - - if ( 'custom' === $template->source ) { - $update = true; - $result = wp_update_post( wp_slash( (array) $changes ), false ); - } else { - $update = false; - $post_before = null; - $result = wp_insert_post( wp_slash( (array) $changes ), false ); - } - - if ( is_wp_error( $result ) ) { - if ( 'db_update_error' === $result->get_error_code() ) { - $result->add_data( array( 'status' => 500 ) ); - } else { - $result->add_data( array( 'status' => 400 ) ); - } - return $result; - } - - $template = gutenberg_get_block_template( $request['id'], $this->post_type ); - $fields_update = $this->update_additional_fields_for_object( $template, $request ); - if ( is_wp_error( $fields_update ) ) { - return $fields_update; - } - - $request->set_param( 'context', 'edit' ); - - $post = get_post( $template->wp_id ); - /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ - do_action( "rest_after_insert_{$this->post_type}", $post, $request, false ); - - wp_after_insert_post( $post, $update, $post_before ); - - $response = $this->prepare_item_for_response( $template, $request ); - - return rest_ensure_response( $response ); - } - - /** - * Prepares a single template for create or update. - * - * @param WP_REST_Request $request Request object. - * @return stdClass Changes to pass to wp_update_post. - */ - protected function prepare_item_for_database( $request ) { - $template = $request['id'] ? gutenberg_get_block_template( $request['id'], $this->post_type ) : null; - $changes = new stdClass(); - if ( null === $template ) { - $changes->post_type = $this->post_type; - $changes->post_status = 'publish'; - $changes->tax_input = array( - 'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : get_stylesheet(), - ); - } elseif ( 'custom' !== $template->source ) { - $changes->post_name = $template->slug; - $changes->post_type = $this->post_type; - $changes->post_status = 'publish'; - $changes->tax_input = array( - 'wp_theme' => $template->theme, - ); - $changes->meta_input = array( - 'origin' => $template->source, - ); - } else { - $changes->post_name = $template->slug; - $changes->ID = $template->wp_id; - $changes->post_status = 'publish'; - } - if ( isset( $request['content'] ) ) { - $changes->post_content = $request['content']; - } elseif ( null !== $template && 'custom' !== $template->source ) { - $changes->post_content = $template->content; - } - if ( isset( $request['title'] ) ) { - $changes->post_title = $request['title']; - } elseif ( null !== $template && 'custom' !== $template->source ) { - $changes->post_title = $template->title; - } - if ( isset( $request['description'] ) ) { - $changes->post_excerpt = $request['description']; - } elseif ( null !== $template && 'custom' !== $template->source ) { - $changes->post_excerpt = $template->description; - } - - if ( 'wp_template' === $this->post_type ) { - if ( isset( $request['is_wp_suggestion'] ) ) { - $changes->meta_input = wp_parse_args( - array( - 'is_wp_suggestion' => $request['is_wp_suggestion'], - ), - $changes->meta_input = array() - ); - } - } - if ( 'wp_template_part' === $this->post_type ) { - if ( isset( $request['area'] ) ) { - $changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $request['area'] ); - } elseif ( null !== $template && 'custom' !== $template->source && $template->area ) { - $changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $template->area ); - } elseif ( ! $template->area ) { - $changes->tax_input['wp_template_part_area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED; - } - } - - if ( ! empty( $request['author'] ) ) { - $post_author = (int) $request['author']; - - if ( get_current_user_id() !== $post_author ) { - $user_obj = get_userdata( $post_author ); - - if ( ! $user_obj ) { - return new WP_Error( - 'rest_invalid_author', - __( 'Invalid author ID.', 'gutenberg' ), - array( 'status' => 400 ) - ); - } - } - - $changes->post_author = $post_author; - } - return $changes; - } -} diff --git a/lib/compat/wordpress-6.1/date-settings.php b/lib/compat/wordpress-6.1/date-settings.php deleted file mode 100644 index bdc65ede58cc3e..00000000000000 --- a/lib/compat/wordpress-6.1/date-settings.php +++ /dev/null @@ -1,82 +0,0 @@ -get_data( 'wp-date', 'after' ); - if ( $inline_scripts ) { - foreach ( $inline_scripts as $index => $inline_script ) { - if ( str_starts_with( $inline_script, 'wp.date.setSettings' ) ) { - unset( $scripts->registered['wp-date']->extra['after'][ $index ] ); - } - } - } - - // Calculate the timezone abbr (EDT, PST) if possible. - $timezone_string = get_option( 'timezone_string', 'UTC' ); - $timezone_abbr = ''; - - if ( ! empty( $timezone_string ) ) { - $timezone_date = new DateTime( 'now', new DateTimeZone( $timezone_string ) ); - $timezone_abbr = $timezone_date->format( 'T' ); - } - - $scripts->add_inline_script( - 'wp-date', - sprintf( - 'wp.date.setSettings( %s );', - wp_json_encode( - array( - 'l10n' => array( - 'locale' => get_user_locale(), - 'months' => array_values( $wp_locale->month ), - 'monthsShort' => array_values( $wp_locale->month_abbrev ), - 'weekdays' => array_values( $wp_locale->weekday ), - 'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ), - 'meridiem' => (object) $wp_locale->meridiem, - 'relative' => array( - /* translators: %s: Duration. */ - 'future' => __( '%s from now', 'gutenberg' ), - /* translators: %s: Duration. */ - 'past' => __( '%s ago', 'gutenberg' ), - ), - 'startOfWeek' => (int) get_option( 'start_of_week', 0 ), - ), - 'formats' => array( - /* translators: Time format, see https://www.php.net/manual/datetime.format.php */ - 'time' => get_option( 'time_format', __( 'g:i a', 'gutenberg' ) ), - /* translators: Date format, see https://www.php.net/manual/datetime.format.php */ - 'date' => get_option( 'date_format', __( 'F j, Y', 'gutenberg' ) ), - /* translators: Date/Time format, see https://www.php.net/manual/datetime.format.php */ - 'datetime' => __( 'F j, Y g:i a', 'gutenberg' ), - /* translators: Abbreviated date/time format, see https://www.php.net/manual/datetime.format.php */ - 'datetimeAbbreviated' => __( 'M j, Y g:i a', 'gutenberg' ), - ), - 'timezone' => array( - 'offset' => get_option( 'gmt_offset', 0 ), - 'string' => $timezone_string, - 'abbr' => $timezone_abbr, - ), - ) - ) - ), - 'after' - ); -} -add_action( 'wp_default_scripts', 'gutenberg_update_date_settings' ); diff --git a/lib/compat/wordpress-6.1/edit-form-blocks.php b/lib/compat/wordpress-6.1/edit-form-blocks.php deleted file mode 100644 index fd2414f228b208..00000000000000 --- a/lib/compat/wordpress-6.1/edit-form-blocks.php +++ /dev/null @@ -1,23 +0,0 @@ -post ) ) { - $preload_paths[] = array( rest_get_route_for_post_type_items( 'wp_template' ), 'OPTIONS' ); - $preload_paths[] = array( '/wp/v2/settings', 'OPTIONS' ); - } - - return $preload_paths; -} -add_filter( 'block_editor_rest_api_preload_paths', 'gutenberg_preload_template_permissions', 10, 2 ); diff --git a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php deleted file mode 100644 index fd6113c7405c4a..00000000000000 --- a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php +++ /dev/null @@ -1,56 +0,0 @@ -get_styles_block_nodes(); - foreach ( $block_nodes as $metadata ) { - $block_css = $tree->get_styles_for_block( $metadata ); - - if ( ! wp_should_load_separate_core_block_assets() ) { - wp_add_inline_style( 'global-styles', $block_css ); - continue; - } - - $stylesheet_handle = 'global-styles'; - if ( isset( $metadata['name'] ) ) { - // These block styles are added on block_render. - // This hooks inline CSS to them so that they are loaded conditionally - // based on whether or not the block is used on the page. - if ( str_starts_with( $metadata['name'], 'core/' ) ) { - $block_name = str_replace( 'core/', '', $metadata['name'] ); - $stylesheet_handle = 'wp-block-' . $block_name; - } - wp_add_inline_style( $stylesheet_handle, $block_css ); - } - - // The likes of block element styles from theme.json do not have $metadata['name'] set. - if ( ! isset( $metadata['name'] ) && ! empty( $metadata['path'] ) ) { - $result = array_values( - array_filter( - $metadata['path'], - function ( $item ) { - if ( str_contains( $item, 'core/' ) ) { - return true; - } - return false; - } - ) - ); - if ( isset( $result[0] ) ) { - if ( str_starts_with( $result[0], 'core/' ) ) { - $block_name = str_replace( 'core/', '', $result[0] ); - $stylesheet_handle = 'wp-block-' . $block_name; - } - wp_add_inline_style( $stylesheet_handle, $block_css ); - } - } - } -} diff --git a/lib/compat/wordpress-6.1/persisted-preferences.php b/lib/compat/wordpress-6.1/persisted-preferences.php deleted file mode 100644 index 2263820e9911b4..00000000000000 --- a/lib/compat/wordpress-6.1/persisted-preferences.php +++ /dev/null @@ -1,104 +0,0 @@ -get_blog_prefix() . 'persisted_preferences'; - - register_meta( - 'user', - $meta_key, - array( - 'type' => 'object', - 'single' => true, - 'show_in_rest' => array( - 'name' => 'persisted_preferences', - 'type' => 'object', - 'schema' => array( - 'type' => 'object', - 'context' => array( 'edit' ), - 'properties' => array( - '_modified' => array( - 'description' => __( 'The date and time the preferences were updated.', 'default' ), - 'type' => 'string', - 'format' => 'date-time', - 'readonly' => false, - ), - ), - 'additionalProperties' => true, - ), - ), - ) - ); -} - -add_action( 'init', 'gutenberg_register_persisted_preferences_meta' ); - -/** - * Configures the preferences package to use user meta persistence. - */ -function gutenberg_configure_persisted_preferences() { - $user_id = get_current_user_id(); - if ( empty( $user_id ) ) { - return; - } - - global $wpdb; - $meta_key = $wpdb->get_blog_prefix() . 'persisted_preferences'; - - $preload_data = get_user_meta( $user_id, $meta_key, true ); - - wp_add_inline_script( - 'wp-preferences', - sprintf( - '( function() { - var serverData = %s; - var userId = "%s"; - var persistenceLayer = wp.preferencesPersistence.__unstableCreatePersistenceLayer( serverData, userId ); - var preferencesStore = wp.preferences.store; - wp.data.dispatch( preferencesStore ).setPersistenceLayer( persistenceLayer ); - } ) ();', - wp_json_encode( $preload_data ), - $user_id - ), - 'after' - ); -} - -add_action( 'admin_init', 'gutenberg_configure_persisted_preferences' ); - -/** - * Register dependencies for the inline script that configures the persistence layer. - * - * Note: When porting this to core update the code here: - * https://github.com/WordPress/wordpress-develop/blob/d2ab3d183740c3d1252cb921b18005495007e022/src/wp-includes/script-loader.php#L251-L258 - * - * And make the same update to the gutenberg client assets file here: - * https://github.com/WordPress/gutenberg/blob/3f3c8df23c70a37b7ac4dddebc82030362133593/lib/client-assets.php#L242-L254 - * - * The update should be adding a new case like this like this: - * ``` - * case 'wp-preferences': - * array_push( $dependencies, 'wp-preferences-persistence' ); - * break; - * ``` - * - * @param WP_Scripts $scripts An instance of WP_Scripts. - */ -function gutenberg_update_preferences_persistence_deps( $scripts ) { - $persistence_script = $scripts->query( 'wp-preferences', 'registered' ); - if ( isset( $persistence_script->deps ) ) { - array_push( $persistence_script->deps, 'wp-preferences-persistence' ); - } -} - -add_action( 'wp_default_scripts', 'gutenberg_update_preferences_persistence_deps', 11 ); diff --git a/lib/compat/wordpress-6.1/rest-api.php b/lib/compat/wordpress-6.1/rest-api.php deleted file mode 100644 index f2293e5169a9c4..00000000000000 --- a/lib/compat/wordpress-6.1/rest-api.php +++ /dev/null @@ -1,70 +0,0 @@ -data['icon'] = $post_type->menu_icon; - return $response; -} -add_filter( 'rest_prepare_post_type', 'gutenberg_update_post_types_rest_response', 10, 2 ); - -/** - * Exposes the site logo URL through the WordPress REST API. - * - * This is used for fetching this information when user has no rights - * to update settings. - * - * Note: Backports into wp-includes/rest-api/class-wp-rest-server.php file. - * - * @param WP_REST_Response $response REST API response. - * @return WP_REST_Response $response REST API response. - */ -function gutenberg_add_site_icon_url_to_index( WP_REST_Response $response ) { - $response->data['site_icon_url'] = get_site_icon_url(); - - return $response; -} -add_action( 'rest_index', 'gutenberg_add_site_icon_url_to_index' ); - -/** - * Returns the has_archive post type field. - * - * @param array $type The response data. - * @param string $field_name The field name. The function handles field has_archive. - */ -function gutenberg_get_post_type_has_archive_field( $type, $field_name ) { - if ( ! empty( $type ) && ! empty( $type['slug'] ) && 'has_archive' === $field_name ) { - $post_type_object = get_post_type_object( $type['slug'] ); - return $post_type_object->has_archive; - } -} - -/** - * Registers the has_archive post type REST API field. - */ -function gutenberg_register_has_archive_on_post_types_endpoint() { - register_rest_field( - 'type', - 'has_archive', - array( - 'get_callback' => 'gutenberg_get_post_type_has_archive_field', - 'schema' => array( - 'description' => __( 'If the value is a string, the value will be used as the archive slug. If the value is false the post type has no archive.', 'gutenberg' ), - 'type' => array( 'string', 'boolean' ), - 'context' => array( 'view', 'edit' ), - ), - ) - ); -} -add_action( 'rest_api_init', 'gutenberg_register_has_archive_on_post_types_endpoint' ); diff --git a/lib/compat/wordpress-6.1/script-loader.php b/lib/compat/wordpress-6.1/script-loader.php deleted file mode 100644 index 4948882c113a88..00000000000000 --- a/lib/compat/wordpress-6.1/script-loader.php +++ /dev/null @@ -1,125 +0,0 @@ - file_get_contents( $classic_theme_styles ), - 'baseURL' => get_theme_file_uri( $classic_theme_styles ), - '__unstableType' => 'theme', - 'isGlobalStyles' => false, - ); - - return $editor_settings; -} -add_filter( 'block_editor_settings_all', 'gutenberg_add_editor_classic_theme_styles' ); diff --git a/lib/compat/wordpress-6.1/template-parts-screen.php b/lib/compat/wordpress-6.1/template-parts-screen.php deleted file mode 100644 index 1555b6ab1e250b..00000000000000 --- a/lib/compat/wordpress-6.1/template-parts-screen.php +++ /dev/null @@ -1,217 +0,0 @@ - 'wp_template_part' ), - admin_url( 'themes.php?page=gutenberg-template-parts' ) - ); - wp_safe_redirect( $redirect_url ); - exit; - } -} -add_action( 'load-appearance_page_gutenberg-template-parts', 'gutenberg_template_parts_screen_permissions' ); - -/** - * Initialize the editor for the screen. Most of this is copied from `site-editor.php`. - * - * Note: Parts that need to be ported back should have inline comments. - * - * @param string $hook Current page hook. - * @return void - */ -function gutenberg_template_parts_screen_init( $hook ) { - global $current_screen, $editor_styles; - - if ( 'appearance_page_gutenberg-template-parts' !== $hook ) { - return; - } - - // Flag that we're loading the block editor. - $current_screen->is_block_editor( true ); - - // Default to is-fullscreen-mode to avoid jumps in the UI. - add_filter( - 'admin_body_class', - static function( $classes ) { - return "$classes is-fullscreen-mode"; - } - ); - - $indexed_template_types = array(); - foreach ( get_default_block_template_types() as $slug => $template_type ) { - $template_type['slug'] = (string) $slug; - $indexed_template_types[] = $template_type; - } - - $block_editor_context = new WP_Block_Editor_Context( array( 'name' => 'core/edit-site' ) ); - $custom_settings = array( - 'siteUrl' => site_url(), - 'postsPerPage' => get_option( 'posts_per_page' ), - 'styles' => get_block_editor_theme_styles(), - 'defaultTemplateTypes' => $indexed_template_types, - 'defaultTemplatePartAreas' => get_allowed_block_template_part_areas(), - 'supportsLayout' => wp_theme_has_theme_json(), - 'supportsTemplatePartsMode' => ! wp_is_block_theme() && current_theme_supports( 'block-template-parts' ), - ); - - // Add additional back-compat patterns registered by `current_screen` et al. - $custom_settings['__experimentalAdditionalBlockPatterns'] = WP_Block_Patterns_Registry::get_instance()->get_all_registered( true ); - $custom_settings['__experimentalAdditionalBlockPatternCategories'] = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered( true ); - - $editor_settings = get_block_editor_settings( $custom_settings, $block_editor_context ); - - if ( isset( $_GET['postType'] ) && ! isset( $_GET['postId'] ) ) { - $post_type = get_post_type_object( $_GET['postType'] ); - if ( ! $post_type ) { - wp_die( __( 'Invalid post type.', 'gutenberg' ) ); - } - } - - $active_global_styles_id = WP_Theme_JSON_Resolver::get_user_global_styles_post_id(); - $active_theme = get_stylesheet(); - $preload_paths = array( - array( '/wp/v2/media', 'OPTIONS' ), - '/wp/v2/types?context=view', - '/wp/v2/types/wp_template?context=edit', - '/wp/v2/types/wp_template-part?context=edit', - '/wp/v2/templates?context=edit&per_page=-1', - '/wp/v2/template-parts?context=edit&per_page=-1', - '/wp/v2/themes?context=edit&status=active', - '/wp/v2/global-styles/' . $active_global_styles_id . '?context=edit', - '/wp/v2/global-styles/' . $active_global_styles_id, - '/wp/v2/global-styles/themes/' . $active_theme, - ); - - block_editor_rest_api_preload( $preload_paths, $block_editor_context ); - - wp_add_inline_script( - 'wp-edit-site', - sprintf( - 'wp.domReady( function() { - wp.editSite.initializeEditor( "site-editor", %s ); - } );', - wp_json_encode( $editor_settings ) - ) - ); - - // Preload server-registered block schemas. - wp_add_inline_script( - 'wp-blocks', - 'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . wp_json_encode( get_block_editor_server_block_settings() ) . ');' - ); - - wp_add_inline_script( - 'wp-blocks', - sprintf( 'wp.blocks.setCategories( %s );', wp_json_encode( get_block_categories( $block_editor_context ) ) ) - ); - - wp_enqueue_script( 'wp-edit-site' ); - wp_enqueue_script( 'wp-format-library' ); - wp_enqueue_style( 'wp-edit-site' ); - wp_enqueue_style( 'wp-format-library' ); - wp_enqueue_media(); - - if ( - current_theme_supports( 'wp-block-styles' ) || - ( ! is_array( $editor_styles ) || count( $editor_styles ) === 0 ) - ) { - wp_enqueue_style( 'wp-block-library-theme' ); - } - - /** This action is documented in wp-admin/edit-form-blocks.php */ - do_action( 'enqueue_block_editor_assets' ); -} -add_action( 'admin_enqueue_scripts', 'gutenberg_template_parts_screen_init' ); - -/** - * The main entry point for the screen. - * - * @return void - */ -function gutenberg_template_parts_screen_render() { - echo '
'; -} - -/** - * Register the new theme feature. - * - * Migrates into `create_initial_theme_features` method. - * - * @return void - */ -function gutenberg_register_template_parts_theme_feature() { - register_theme_feature( - 'block-template-parts', - array( - 'description' => __( 'Whether a theme uses block-based template parts.', 'gutenberg' ), - 'show_in_rest' => true, - ) - ); -} -add_action( 'setup_theme', 'gutenberg_register_template_parts_theme_feature', 5 ); diff --git a/lib/compat/wordpress-6.1/theme.php b/lib/compat/wordpress-6.1/theme.php deleted file mode 100644 index a36e48a14db96b..00000000000000 --- a/lib/compat/wordpress-6.1/theme.php +++ /dev/null @@ -1,47 +0,0 @@ - __( 'Whether the theme disables generated layout styles.', 'gutenberg' ), - 'show_in_rest' => true, - ) - ); -} -add_action( 'setup_theme', 'gutenberg_create_initial_theme_features', 0 ); - -if ( ! function_exists( 'wp_theme_get_element_class_name' ) ) { - /** - * Given an element name, returns a class name. - * Alias from WP_Theme_JSON_Gutenberg::get_element_class_name. - * - * @param string $element The name of the element. - * - * @return string The name of the class. - * - * @since 6.1.0 - */ - function wp_theme_get_element_class_name( $element ) { - return WP_Theme_JSON_Gutenberg::get_element_class_name( $element ); - } -} diff --git a/lib/compat/wordpress-6.1/wp-theme-get-post-templates.php b/lib/compat/wordpress-6.1/wp-theme-get-post-templates.php deleted file mode 100644 index e006a2ccca3de4..00000000000000 --- a/lib/compat/wordpress-6.1/wp-theme-get-post-templates.php +++ /dev/null @@ -1,44 +0,0 @@ -slug; - }, - gutenberg_get_block_templates( array( 'post_type' => $post_type ), 'wp_template' ) - ); - $core_block_templates = array_map( - function( $template ) { - return $template->slug; - }, - get_block_templates( array( 'post_type' => $post_type ), 'wp_template' ) - ); - $templates_to_exclude = array_diff( $core_block_templates, $gutenberg_block_templates ); - foreach ( $templates_to_exclude as $template_slug ) { - unset( $templates[ $template_slug ] ); - } - return $templates; -} -add_filter( 'theme_templates', 'gutenberg_load_block_page_templates', 10, 4 ); diff --git a/lib/compat/wordpress-6.2/block-editor-settings.php b/lib/compat/wordpress-6.2/block-editor-settings.php deleted file mode 100644 index 2fd8dc1e2f6bb8..00000000000000 --- a/lib/compat/wordpress-6.2/block-editor-settings.php +++ /dev/null @@ -1,28 +0,0 @@ - gutenberg_get_global_styles_custom_css(), - '__unstableType' => 'user', - 'isGlobalStyles' => true, - ); - } - - return $settings; -} - -add_filter( 'block_editor_settings_all', 'gutenberg_get_block_editor_settings_6_2', PHP_INT_MAX ); diff --git a/lib/compat/wordpress-6.2/block-patterns.php b/lib/compat/wordpress-6.2/block-patterns.php index 9a61041ee291b4..2112a8c2c890f1 100644 --- a/lib/compat/wordpress-6.2/block-patterns.php +++ b/lib/compat/wordpress-6.2/block-patterns.php @@ -433,7 +433,7 @@ function gutenberg_register_remote_theme_patterns() { return; } - $pattern_settings = WP_Theme_JSON_Resolver::get_theme_data()->get_patterns(); + $pattern_settings = gutenberg_get_remote_theme_patterns(); if ( empty( $pattern_settings ) ) { return; } diff --git a/lib/compat/wordpress-6.2/block-template-utils.php b/lib/compat/wordpress-6.2/block-template-utils.php index 991b91c70e09e3..7611b9be5cf1bb 100644 --- a/lib/compat/wordpress-6.2/block-template-utils.php +++ b/lib/compat/wordpress-6.2/block-template-utils.php @@ -95,3 +95,146 @@ function gutenberg_get_template_hierarchy( $slug, $is_custom = false, $template_ $template_hierarchy[] = 'index'; return $template_hierarchy; } + + +/** + * Updates the list of default template types, containing their + * localized titles and descriptions. + * + * We will only need to update `get_default_block_template_types` function. + * + * @param array $default_template_types The default template types. + * + * @return array The default template types. + */ +function gutenberg_get_default_block_template_types( $default_template_types ) { + if ( isset( $default_template_types['index'] ) ) { + $default_template_types['index'] = array( + 'title' => _x( 'Index', 'Template name', 'gutenberg' ), + 'description' => __( + 'Used as a fallback template for all pages when a more specific template is not defined.', + 'gutenberg' + ), + ); + } + if ( isset( $default_template_types['home'] ) ) { + $default_template_types['home'] = array( + 'title' => _x( 'Home', 'Template name', 'gutenberg' ), + 'description' => __( + 'Displays the latest posts as either the site homepage or a custom page defined under reading settings. If it exists, the Front Page template overrides this template when posts are shown on the front page.', + 'gutenberg' + ), + ); + } + if ( isset( $default_template_types['front-page'] ) ) { + $default_template_types['front-page'] = array( + 'title' => _x( 'Front Page', 'Template name', 'gutenberg' ), + 'description' => __( + "Displays your site's front page, whether it is set to display latest posts or a static page. The Front Page template takes precedence over all templates.", + 'gutenberg' + ), + ); + } + if ( isset( $default_template_types['singular'] ) ) { + $default_template_types['singular'] = array( + 'title' => _x( 'Singular', 'Template name', 'gutenberg' ), + 'description' => __( + 'Displays any single entry, such as a post or a page. This template will serve as a fallback when a more specific template (e.g., Single Post, Page, or Attachment) cannot be found.', + 'gutenberg' + ), + ); + } + if ( isset( $default_template_types['single'] ) ) { + $default_template_types['single'] = array( + 'title' => _x( 'Single', 'Template name', 'gutenberg' ), + 'description' => __( 'Displays single posts on your website unless a custom template has been applied to that post or a dedicated template exists.', 'gutenberg' ), + ); + } + if ( isset( $default_template_types['page'] ) ) { + $default_template_types['page'] = array( + 'title' => _x( 'Page', 'Template name', 'gutenberg' ), + 'description' => __( 'Display all static pages unless a custom template has been applied or a dedicated template exists.', 'gutenberg' ), + ); + } + if ( isset( $default_template_types['archive'] ) ) { + $default_template_types['archive'] = array( + 'title' => _x( 'Archive', 'Template name', 'gutenberg' ), + 'description' => __( + 'Displays any archive, including posts by a single author, category, tag, taxonomy, custom post type, and date. This template will serve as a fallback when more specific templates (e.g., Category or Tag) cannot be found.', + 'gutenberg' + ), + ); + } + if ( isset( $default_template_types['author'] ) ) { + $default_template_types['author'] = array( + 'title' => _x( 'Author', 'Template name', 'gutenberg' ), + 'description' => __( + "Displays a single author's post archive. This template will serve as a fallback when a more a specific template (e.g., Author: Admin) cannot be found.", + 'gutenberg' + ), + ); + } + if ( isset( $default_template_types['category'] ) ) { + $default_template_types['category'] = array( + 'title' => _x( 'Category', 'Template name', 'gutenberg' ), + 'description' => __( + 'Displays a post category archive. This template will serve as a fallback when more specific template (e.g., Category: Recipes) cannot be found.', + 'gutenberg' + ), + ); + } + if ( isset( $default_template_types['taxonomy'] ) ) { + $default_template_types['taxonomy'] = array( + 'title' => _x( 'Taxonomy', 'Template name', 'gutenberg' ), + 'description' => __( + 'Displays a custom taxonomy archive. Like categories and tags, taxonomies have terms which you use to classify things. For example: a taxonomy named "Art" can have multiple terms, such as "Modern" and "18th Century." This template will serve as a fallback when a more specific template (e.g, Taxonomy: Art) cannot be found.', + 'gutenberg' + ), + ); + } + if ( isset( $default_template_types['date'] ) ) { + $default_template_types['date'] = array( + 'title' => _x( 'Date', 'Template name', 'gutenberg' ), + 'description' => __( 'Displays a post archive when a specific date is visited (e.g., example.com/2023/).', 'gutenberg' ), + ); + } + if ( isset( $default_template_types['tag'] ) ) { + $default_template_types['tag'] = array( + 'title' => _x( 'Tag', 'Template name', 'gutenberg' ), + 'description' => __( + 'Displays a post tag archive. This template will serve as a fallback when more specific template (e.g., Tag: Pizza) cannot be found.', + 'gutenberg' + ), + ); + } + if ( isset( $default_template_types['attachment'] ) ) { + $default_template_types['attachment'] = array( + 'title' => _x( 'Media', 'Template name', 'gutenberg' ), + 'description' => __( 'Displays when a visitor views the dedicated page that exists for any media attachment.', 'gutenberg' ), + ); + } + if ( isset( $default_template_types['search'] ) ) { + $default_template_types['search'] = array( + 'title' => _x( 'Search', 'Template name', 'gutenberg' ), + 'description' => __( 'Displays when a visitor performs a search on your website.', 'gutenberg' ), + ); + } + if ( isset( $default_template_types['privacy-policy'] ) ) { + $default_template_types['privacy-policy'] = array( + 'title' => _x( 'Privacy Policy', 'Template name', 'gutenberg' ), + 'description' => __( + "Displays your site's Privacy Policy page.", + 'gutenberg' + ), + ); + } + if ( isset( $default_template_types['404'] ) ) { + $default_template_types['404'] = array( + 'title' => _x( '404', 'Template name', 'gutenberg' ), + 'description' => __( 'Displays when a visitor views a non-existent page, such as a dead link or a mistyped URL.', 'gutenberg' ), + ); + } + + return $default_template_types; +} +add_filter( 'default_template_types', 'gutenberg_get_default_block_template_types', 10 ); diff --git a/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php b/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php index ddaac89d13a181..70a9ae397fe8d6 100644 --- a/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php +++ b/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php @@ -13,7 +13,7 @@ * * @see WP_REST_Controller */ -class Gutenberg_REST_Block_Patterns_Controller_6_2 extends Gutenberg_REST_Block_Patterns_Controller_6_1 { +class Gutenberg_REST_Block_Patterns_Controller_6_2 extends WP_REST_Block_Patterns_Controller { /** * Defines whether remote patterns should be loaded. * diff --git a/lib/compat/wordpress-6.2/default-filters.php b/lib/compat/wordpress-6.2/default-filters.php index 4698ead7f9bc30..7e797af3e43b1f 100644 --- a/lib/compat/wordpress-6.2/default-filters.php +++ b/lib/compat/wordpress-6.2/default-filters.php @@ -18,8 +18,16 @@ */ /** - * When backporting to core, the existing filters hooked to WP_Theme_JSON_Resolver::clean_cached_data() - * need to be removed. + * This is a temporary fix to ensure that the block editor styles are enqueued + * in the order the iframe expects. + * + * The wp_enqueue_registered_block_scripts_and_styles callback has been removed in core + * as of https://github.com/WordPress/wordpress-develop/pull/4356. + * + * However, Gutenberg supports WordPress 6.1 and 6.2, which still have this callback. + * Hence, why we remove it first and then re-add it. + * + * This way we make sure it still works the same in WordPress trunk, 6.1 and 6.2. */ -add_action( 'start_previewing_theme', '_gutenberg_clean_theme_json_caches' ); -add_action( 'switch_theme', '_gutenberg_clean_theme_json_caches' ); +remove_action( 'enqueue_block_editor_assets', 'wp_enqueue_registered_block_scripts_and_styles' ); +add_action( 'enqueue_block_editor_assets', 'wp_enqueue_registered_block_scripts_and_styles', 1 ); diff --git a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php index e02a0466a0b98f..eb1481a3808b68 100644 --- a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php @@ -59,176 +59,6 @@ function wp_theme_has_theme_json_clean_cache() { } } -/** - * Gets the global styles custom css from theme.json. - * - * @return string - */ -function gutenberg_get_global_styles_custom_css() { - // Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme developers workflow. - $can_use_cached = ! WP_DEBUG; - $cache_key = 'gutenberg_get_global_custom_css'; - $cache_group = 'theme_json'; - if ( $can_use_cached ) { - $cached = wp_cache_get( $cache_key, $cache_group ); - if ( $cached ) { - return $cached; - } - } - - if ( ! wp_theme_has_theme_json() ) { - return ''; - } - - $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); - $stylesheet = $tree->get_custom_css(); - - if ( $can_use_cached ) { - wp_cache_set( $cache_key, $stylesheet, $cache_group ); - } - - return $stylesheet; -} - -/** - * Returns the stylesheet resulting of merging core, theme, and user data. - * - * @param array $types Types of styles to load. Optional. - * It accepts as values: 'variables', 'presets', 'styles', 'base-layout-styles. - * If empty, it'll load the following: - * - for themes without theme.json: 'variables', 'presets', 'base-layout-styles'. - * - for themes with theme.json: 'variables', 'presets', 'styles'. - * - * @return string Stylesheet. - */ -function gutenberg_get_global_stylesheet( $types = array() ) { - // Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme developers workflow. - $can_use_cached = empty( $types ) && ! WP_DEBUG; - $cache_key = 'gutenberg_get_global_stylesheet'; - $cache_group = 'theme_json'; - if ( $can_use_cached ) { - $cached = wp_cache_get( $cache_key, $cache_group ); - if ( $cached ) { - return $cached; - } - } - $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); - $supports_theme_json = wp_theme_has_theme_json(); - if ( empty( $types ) && ! $supports_theme_json ) { - $types = array( 'variables', 'presets', 'base-layout-styles' ); - } elseif ( empty( $types ) ) { - $types = array( 'variables', 'presets', 'styles' ); - } - - /* - * If variables are part of the stylesheet, - * we add them. - * - * This is so themes without a theme.json still work as before 5.9: - * they can override the default presets. - * See https://core.trac.wordpress.org/ticket/54782 - */ - $styles_variables = ''; - if ( in_array( 'variables', $types, true ) ) { - /* - * We only use the default, theme, and custom origins. - * This is because styles for blocks origin are added - * at a later phase (render cycle) so we only render the ones in use. - * @see wp_add_global_styles_for_blocks - */ - $origins = array( 'default', 'theme', 'custom' ); - $styles_variables = $tree->get_stylesheet( array( 'variables' ), $origins ); - $types = array_diff( $types, array( 'variables' ) ); - } - - /* - * For the remaining types (presets, styles), we do consider origins: - * - * - themes without theme.json: only the classes for the presets defined by core - * - themes with theme.json: the presets and styles classes, both from core and the theme - */ - $styles_rest = ''; - if ( ! empty( $types ) ) { - /* - * We only use the default, theme, and custom origins. - * This is because styles for blocks origin are added - * at a later phase (render cycle) so we only render the ones in use. - * @see wp_add_global_styles_for_blocks - */ - $origins = array( 'default', 'theme', 'custom' ); - if ( ! $supports_theme_json ) { - $origins = array( 'default' ); - } - $styles_rest = $tree->get_stylesheet( $types, $origins ); - } - $stylesheet = $styles_variables . $styles_rest; - if ( $can_use_cached ) { - wp_cache_set( $cache_key, $stylesheet, $cache_group ); - } - return $stylesheet; -} - -/** - * Function to get the settings resulting of merging core, theme, and user data. - * - * @param array $path Path to the specific setting to retrieve. Optional. - * If empty, will return all settings. - * @param array $context { - * Metadata to know where to retrieve the $path from. Optional. - * - * @type string $block_name Which block to retrieve the settings from. - * If empty, it'll return the settings for the global context. - * @type string $origin Which origin to take data from. - * Valid values are 'all' (core, theme, and user) or 'base' (core and theme). - * If empty or unknown, 'all' is used. - * } - * - * @return array The settings to retrieve. - */ -function gutenberg_get_global_settings( $path = array(), $context = array() ) { - if ( ! empty( $context['block_name'] ) ) { - $new_path = array( 'blocks', $context['block_name'] ); - foreach ( $path as $subpath ) { - $new_path[] = $subpath; - } - $path = $new_path; - } - - // This is the default value when no origin is provided or when it is 'all'. - $origin = 'custom'; - if ( - ! wp_theme_has_theme_json() || - ( isset( $context['origin'] ) && 'base' === $context['origin'] ) - ) { - $origin = 'theme'; - } - - $cache_group = 'theme_json'; - $cache_key = 'gutenberg_get_global_settings_' . $origin; - $settings = wp_cache_get( $cache_key, $cache_group ); - - if ( false === $settings || WP_DEBUG ) { - $settings = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin )->get_settings(); - wp_cache_set( $cache_key, $settings, $cache_group ); - } - - return _wp_array_get( $settings, $path, $settings ); -} - -/** - * Private function to clean the caches used by gutenberg_get_global_settings method. - * - * @access private - */ -function _gutenberg_clean_theme_json_caches() { - wp_cache_delete( 'wp_theme_has_theme_json', 'theme_json' ); - wp_cache_delete( 'gutenberg_get_global_stylesheet', 'theme_json' ); - wp_cache_delete( 'gutenberg_get_global_settings_custom', 'theme_json' ); - wp_cache_delete( 'gutenberg_get_global_settings_theme', 'theme_json' ); - wp_cache_delete( 'gutenberg_get_global_custom_css', 'theme_json' ); - WP_Theme_JSON_Resolver_Gutenberg::clean_cached_data(); -} - /** * Tell the cache mechanisms not to persist theme.json data across requests. * The data stored under this cache group: diff --git a/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php b/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php index 044d1f0c366587..f06302d4b742ff 100644 --- a/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php +++ b/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php @@ -39,10 +39,10 @@ * * Example: * ```php - * $tags = new WP_HTML_Tag_Processor( $html ); - * if ( $tags->next_tag( array( 'tag_name' => 'option' ) ) ) { - * $tags->set_attribute( 'selected', true ); - * } + * $tags = new WP_HTML_Tag_Processor( $html ); + * if ( $tags->next_tag( 'option' ) ) { + * $tags->set_attribute( 'selected', true ); + * } * ``` * * ### Finding tags @@ -55,13 +55,14 @@ * * If you want to _find whatever the next tag is_: * ```php - * $tags->next_tag(); + * $tags->next_tag(); * ``` * - * | Goal | Query | - * |-----------------------------------------------------------|----------------------------------------------------------------------------| - * | Find any tag. | `$tags->next_tag();` | + * | Goal | Query | + * |-----------------------------------------------------------|---------------------------------------------------------------------------------| + * | Find any tag. | `$tags->next_tag();` | * | Find next image tag. | `$tags->next_tag( array( 'tag_name' => 'img' ) );` | + * | Find next image tag (without passing the array). | `$tags->next_tag( 'img' );` | * | Find next tag containing the `fullwidth` CSS class. | `$tags->next_tag( array( 'class_name' => 'fullwidth' ) );` | * | Find next image tag containing the `fullwidth` CSS class. | `$tags->next_tag( array( 'tag_name' => 'img', 'class_name' => 'fullwidth' ) );` | * @@ -87,17 +88,17 @@ * * Example: * ```php - * // Paint up to the first five DIV or SPAN tags marked with the "jazzy" style. - * $remaining_count = 5; - * while ( $remaining_count > 0 && $tags->next_tag() ) { - * if ( - * ( 'DIV' === $tags->get_tag() || 'SPAN' === $tags->get_tag() ) && - * 'jazzy' === $tags->get_attribute( 'data-style' ) - * ) { - * $tags->add_class( 'theme-style-everest-jazz' ); - * $remaining_count--; - * } + * // Paint up to the first five DIV or SPAN tags marked with the "jazzy" style. + * $remaining_count = 5; + * while ( $remaining_count > 0 && $tags->next_tag() ) { + * if ( + * ( 'DIV' === $tags->get_tag() || 'SPAN' === $tags->get_tag() ) && + * 'jazzy' === $tags->get_attribute( 'data-style' ) + * ) { + * $tags->add_class( 'theme-style-everest-jazz' ); + * $remaining_count--; * } + * } * ``` * * `get_attribute()` will return `null` if the attribute wasn't present @@ -116,10 +117,10 @@ * * Example: * ```php - * if ( $tags->next_tag( array( 'class' => 'wp-group-block' ) ) ) { - * $tags->set_attribute( 'title', 'This groups the contained content.' ); - * $tags->remove_attribute( 'data-test-id' ); - * } + * if ( $tags->next_tag( array( 'class' => 'wp-group-block' ) ) ) { + * $tags->set_attribute( 'title', 'This groups the contained content.' ); + * $tags->remove_attribute( 'data-test-id' ); + * } * ``` * * If `set_attribute()` is called for an existing attribute it will @@ -141,29 +142,29 @@ * * Example: * ```php - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); + * // from `Yippee!` + * // to `Yippee!` + * $tags->add_class( 'is-active' ); * - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); + * // from `Yippee!` + * // to `Yippee!` + * $tags->add_class( 'is-active' ); * - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); + * // from `Yippee!` + * // to `Yippee!` + * $tags->add_class( 'is-active' ); * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); + * // from `` + * // to ` + * $tags->remove_class( 'rugby' ); * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); + * // from `` + * // to ` + * $tags->remove_class( 'rugby' ); * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); + * // from `` + * // to ` + * $tags->remove_class( 'rugby' ); * ``` * * When class changes are enqueued but a direct change to `class` is made via @@ -184,24 +185,24 @@ * bookmark and update it frequently, such as within a loop. * * ```php - * $total_todos = 0; - * while ( $p->next_tag( array( 'tag_name' => 'UL', 'class_name' => 'todo' ) ) ) { - * $p->set_bookmark( 'list-start' ); - * while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) { - * if ( 'UL' === $p->get_tag() && $p->is_tag_closer() ) { - * $p->set_bookmark( 'list-end' ); - * $p->seek( 'list-start' ); - * $p->set_attribute( 'data-contained-todos', (string) $total_todos ); - * $total_todos = 0; - * $p->seek( 'list-end' ); - * break; - * } + * $total_todos = 0; + * while ( $p->next_tag( array( 'tag_name' => 'UL', 'class_name' => 'todo' ) ) ) { + * $p->set_bookmark( 'list-start' ); + * while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) { + * if ( 'UL' === $p->get_tag() && $p->is_tag_closer() ) { + * $p->set_bookmark( 'list-end' ); + * $p->seek( 'list-start' ); + * $p->set_attribute( 'data-contained-todos', (string) $total_todos ); + * $total_todos = 0; + * $p->seek( 'list-end' ); + * break; + * } * - * if ( 'LI' === $p->get_tag() && ! $p->is_tag_closer() ) { - * $total_todos++; - * } + * if ( 'LI' === $p->get_tag() && ! $p->is_tag_closer() ) { + * $total_todos++; * } * } + * } * ``` * * ## Design and limitations @@ -228,11 +229,11 @@ * The Tag Processor's design incorporates a "garbage-in-garbage-out" philosophy. * HTML5 specifies that certain invalid content be transformed into different forms * for display, such as removing null bytes from an input document and replacing - * invalid characters with the Unicode replacement character U+FFFD �. Where errors - * or transformations exist within the HTML5 specification, the Tag Processor leaves - * those invalid inputs untouched, passing them through to the final browser to handle. - * While this implies that certain operations will be non-spec-compliant, such as - * reading the value of an attribute with invalid content, it also preserves a + * invalid characters with the Unicode replacement character `U+FFFD` (visually "�"). + * Where errors or transformations exist within the HTML5 specification, the Tag Processor + * leaves those invalid inputs untouched, passing them through to the final browser + * to handle. While this implies that certain operations will be non-spec-compliant, + * such as reading the value of an attribute with invalid content, it also preserves a * simplicity and efficiency for handling those error cases. * * Most operations within the Tag Processor are designed to minimize the difference @@ -252,9 +253,10 @@ class WP_HTML_Tag_Processor { * The maximum number of bookmarks allowed to exist at * any given time. * - * @see set_bookmark() * @since 6.2.0 * @var int + * + * @see WP_HTML_Tag_Processor::set_bookmark() */ const MAX_BOOKMARKS = 10; @@ -262,9 +264,10 @@ class WP_HTML_Tag_Processor { * Maximum number of times seek() can be called. * Prevents accidental infinite loops. * - * @see seek() * @since 6.2.0 * @var int + * + * @see WP_HTML_Tag_Processor::seek() */ const MAX_SEEK_OPS = 1000; @@ -316,23 +319,6 @@ class WP_HTML_Tag_Processor { */ private $stop_on_tag_closers; - /** - * Holds updated HTML as updates are applied. - * - * Updates and unmodified portions of the input document are - * appended to this value as they are applied. It will hold - * a copy of the updated document up until the point of the - * latest applied update. The fully-updated HTML document - * will comprise this value plus the part of the input document - * which follows that latest update. - * - * @see $bytes_already_copied - * - * @since 6.2.0 - * @var string - */ - private $output_buffer = ''; - /** * How many bytes from the original HTML document have been read and parsed. * @@ -345,23 +331,6 @@ class WP_HTML_Tag_Processor { */ private $bytes_already_parsed = 0; - /** - * How many bytes from the input HTML document have already been - * copied into the output buffer. - * - * Lexical updates are enqueued and processed in batches. Prior - * to any given update in the input document, there might exist - * a span of HTML unaffected by any changes. This span ought to - * be copied verbatim into the output buffer before applying the - * following update. This value will point to the starting byte - * offset in the input document where that unaffected span of - * HTML starts. - * - * @since 6.2.0 - * @var int - */ - private $bytes_already_copied = 0; - /** * Byte offset in input document where current tag name starts. * @@ -420,23 +389,23 @@ class WP_HTML_Tag_Processor { * * Example: * ```php - * // supposing the parser is working through this content - * // and stops after recognizing the `id` attribute - * //
- * // ^ parsing will continue from this point - * $this->attributes = array( - * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ) - * ); - * - * // when picking up parsing again, or when asking to find the - * // `class` attribute we will continue and add to this array - * $this->attributes = array( - * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ), - * 'class' => new WP_HTML_Attribute_Match( 'class', 'outline', 18, 32 ) - * ); - * - * // Note that only the `class` attribute value is stored in the index. - * // That's because it is the only value used by this class at the moment. + * // supposing the parser is working through this content + * // and stops after recognizing the `id` attribute + * //
+ * // ^ parsing will continue from this point + * $this->attributes = array( + * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ) + * ); + * + * // when picking up parsing again, or when asking to find the + * // `class` attribute we will continue and add to this array + * $this->attributes = array( + * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ), + * 'class' => new WP_HTML_Attribute_Match( 'class', 'outline', 18, 32 ) + * ); + * + * // Note that only the `class` attribute value is stored in the index. + * // That's because it is the only value used by this class at the moment. * ``` * * @since 6.2.0 @@ -457,12 +426,12 @@ class WP_HTML_Tag_Processor { * * Example: * ```php - * // Add the `wp-block-group` class, remove the `wp-group` class. - * $classname_updates = array( - * // Indexed by a comparable class name - * 'wp-block-group' => WP_HTML_Tag_Processor::ADD_CLASS, - * 'wp-group' => WP_HTML_Tag_Processor::REMOVE_CLASS - * ); + * // Add the `wp-block-group` class, remove the `wp-group` class. + * $classname_updates = array( + * // Indexed by a comparable class name + * 'wp-block-group' => WP_HTML_Tag_Processor::ADD_CLASS, + * 'wp-group' => WP_HTML_Tag_Processor::REMOVE_CLASS + * ); * ``` * * @since 6.2.0 @@ -511,16 +480,16 @@ class WP_HTML_Tag_Processor { * * Example: * ```php - * // Replace an attribute stored with a new value, indices - * // sourced from the lazily-parsed HTML recognizer. - * $start = $attributes['src']->start; - * $end = $attributes['src']->end; - * $modifications[] = new WP_HTML_Text_Replacement( $start, $end, $new_value ); - * - * // Correspondingly, something like this will appear in this array. - * $lexical_updates = array( - * WP_HTML_Text_Replacement( 14, 28, 'https://my-site.my-domain/wp-content/uploads/2014/08/kittens.jpg' ) - * ); + * // Replace an attribute stored with a new value, indices + * // sourced from the lazily-parsed HTML recognizer. + * $start = $attributes['src']->start; + * $end = $attributes['src']->end; + * $modifications[] = new WP_HTML_Text_Replacement( $start, $end, $new_value ); + * + * // Correspondingly, something like this will appear in this array. + * $lexical_updates = array( + * WP_HTML_Text_Replacement( 14, 28, 'https://my-site.my-domain/wp-content/uploads/2014/08/kittens.jpg' ) + * ); * ``` * * @since 6.2.0 @@ -531,9 +500,10 @@ class WP_HTML_Tag_Processor { /** * Tracks and limits `seek()` calls to prevent accidental infinite loops. * - * @see seek * @since 6.2.0 * @var int + * + * @see WP_HTML_Tag_Processor::seek() */ protected $seek_count = 0; @@ -753,9 +723,10 @@ public function release_bookmark( $name ) { /** * Skips contents of title and textarea tags. * - * @see https://html.spec.whatwg.org/multipage/parsing.html#rcdata-state * @since 6.2.0 * + * @see https://html.spec.whatwg.org/multipage/parsing.html#rcdata-state + * * @param string $tag_name – the lowercase tag name which will close the RCDATA region. * @return bool Whether an end to the RCDATA region was found before the end of the document. */ @@ -775,7 +746,8 @@ private function skip_rcdata( $tag_name ) { return false; } - $at += 2; + $closer_potentially_starts_at = $at; + $at += 2; /* * Find a case-insensitive match to the tag name. @@ -818,7 +790,7 @@ private function skip_rcdata( $tag_name ) { } if ( '>' === $html[ $at ] || '/' === $html[ $at ] ) { - ++$this->bytes_already_parsed; + $this->bytes_already_parsed = $closer_potentially_starts_at; return true; } } @@ -887,7 +859,8 @@ private function skip_script_data() { } if ( '/' === $html[ $at ] ) { - $is_closing = true; + $closer_potentially_starts_at = $at - 1; + $is_closing = true; ++$at; } else { $is_closing = false; @@ -938,7 +911,7 @@ private function skip_script_data() { } if ( $is_closing ) { - $this->bytes_already_parsed = $at; + $this->bytes_already_parsed = $closer_potentially_starts_at; if ( $this->bytes_already_parsed >= $doc_length ) { return false; } @@ -948,7 +921,7 @@ private function skip_script_data() { } if ( '>' === $html[ $this->bytes_already_parsed ] ) { - ++$this->bytes_already_parsed; + $this->bytes_already_parsed = $closer_potentially_starts_at; return true; } } @@ -968,6 +941,7 @@ private function skip_script_data() { * closing `>`; these are left for other methods. * * @since 6.2.0 + * @since 6.2.1 Support abruptly-closed comments, invalid-tag-closer-comments, and empty elements. * * @return bool Whether a tag was found before the end of the document. */ @@ -1036,13 +1010,42 @@ private function parse_next_tag() { '-' === $html[ $at + 2 ] && '-' === $html[ $at + 3 ] ) { - $closer_at = strpos( $html, '-->', $at + 4 ); - if ( false === $closer_at ) { + $closer_at = $at + 4; + // If it's not possible to close the comment then there is nothing more to scan. + if ( strlen( $html ) <= $closer_at ) { return false; } - $at = $closer_at + 3; - continue; + // Abruptly-closed empty comments are a sequence of dashes followed by `>`. + $span_of_dashes = strspn( $html, '-', $closer_at ); + if ( '>' === $html[ $closer_at + $span_of_dashes ] ) { + $at = $closer_at + $span_of_dashes + 1; + continue; + } + + /* + * Comments may be closed by either a --> or an invalid --!>. + * The first occurrence closes the comment. + * + * See https://html.spec.whatwg.org/#parse-error-incorrectly-closed-comment + */ + $closer_at--; // Pre-increment inside condition below reduces risk of accidental infinite looping. + while ( ++$closer_at < strlen( $html ) ) { + $closer_at = strpos( $html, '--', $closer_at ); + if ( false === $closer_at ) { + return false; + } + + if ( $closer_at + 2 < strlen( $html ) && '>' === $html[ $closer_at + 2 ] ) { + $at = $closer_at + 3; + continue 2; + } + + if ( $closer_at + 3 < strlen( $html ) && '!' === $html[ $closer_at + 2 ] && '>' === $html[ $closer_at + 3 ] ) { + $at = $closer_at + 4; + continue 2; + } + } } /* @@ -1101,9 +1104,19 @@ private function parse_next_tag() { continue; } + /* + * is a missing end tag name, which is ignored. + * + * See https://html.spec.whatwg.org/#parse-error-missing-end-tag-name + */ + if ( '>' === $html[ $at + 1 ] ) { + $at++; + continue; + } + /* * - * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state + * See https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state */ if ( '?' === $html[ $at + 1 ] ) { $closer_at = strpos( $html, '>', $at + 2 ); @@ -1115,6 +1128,22 @@ private function parse_next_tag() { continue; } + /* + * If a non-alpha starts the tag name in a tag closer it's a comment. + * Find the first `>`, which closes the comment. + * + * See https://html.spec.whatwg.org/#parse-error-invalid-first-character-of-tag-name + */ + if ( $this->is_closing_tag ) { + $closer_at = strpos( $html, '>', $at + 3 ); + if ( false === $closer_at ) { + return false; + } + + $at = $closer_at + 1; + continue; + } + ++$at; } @@ -1244,8 +1273,7 @@ private function skip_whitespace() { * @return void */ private function after_tag() { - $this->class_name_updates_to_attributes_updates(); - $this->apply_attributes_updates(); + $this->get_updated_html(); $this->tag_name_starts_at = null; $this->tag_name_length = null; $this->tag_ends_at = null; @@ -1257,11 +1285,11 @@ private function after_tag() { * Converts class name updates into tag attributes updates * (they are accumulated in different data formats for performance). * - * @see $lexical_updates - * @see $classname_updates - * * @since 6.2.0 * + * @see WP_HTML_Tag_Processor::$lexical_updates + * @see WP_HTML_Tag_Processor::$classname_updates + * * @return void */ private function class_name_updates_to_attributes_updates() { @@ -1401,14 +1429,19 @@ private function class_name_updates_to_attributes_updates() { * Applies attribute updates to HTML document. * * @since 6.2.0 + * @since 6.2.1 Accumulates shift for internal cursor and passed pointer. + * @since 6.3.0 Invalidate any bookmarks whose targets are overwritten. * - * @return void + * @param int $shift_this_point Accumulate and return shift for this position. + * @return int How many bytes the given pointer moved in response to the updates. */ - private function apply_attributes_updates() { + private function apply_attributes_updates( $shift_this_point = 0 ) { if ( ! count( $this->lexical_updates ) ) { - return; + return 0; } + $accumulated_shift_for_given_point = 0; + /* * Attribute updates can be enqueued in any order but updates * to the document must occur in lexical order; that is, each @@ -1421,17 +1454,33 @@ private function apply_attributes_updates() { */ usort( $this->lexical_updates, array( self::class, 'sort_start_ascending' ) ); + $bytes_already_copied = 0; + $output_buffer = ''; foreach ( $this->lexical_updates as $diff ) { - $this->output_buffer .= substr( $this->html, $this->bytes_already_copied, $diff->start - $this->bytes_already_copied ); - $this->output_buffer .= $diff->text; - $this->bytes_already_copied = $diff->end; + $shift = strlen( $diff->text ) - ( $diff->end - $diff->start ); + + // Adjust the cursor position by however much an update affects it. + if ( $diff->start <= $this->bytes_already_parsed ) { + $this->bytes_already_parsed += $shift; + } + + // Accumulate shift of the given pointer within this function call. + if ( $diff->start <= $shift_this_point ) { + $accumulated_shift_for_given_point += $shift; + } + + $output_buffer .= substr( $this->html, $bytes_already_copied, $diff->start - $bytes_already_copied ); + $output_buffer .= $diff->text; + $bytes_already_copied = $diff->end; } + $this->html = $output_buffer . substr( $this->html, $bytes_already_copied ); + /* * Adjust bookmark locations to account for how the text * replacements adjust offsets in the input document. */ - foreach ( $this->bookmarks as $bookmark ) { + foreach ( $this->bookmarks as $bookmark_name => $bookmark ) { /* * Each lexical update which appears before the bookmark's endpoints * might shift the offsets for those endpoints. Loop through each change @@ -1442,20 +1491,22 @@ private function apply_attributes_updates() { $tail_delta = 0; foreach ( $this->lexical_updates as $diff ) { - $update_head = $bookmark->start >= $diff->start; - $update_tail = $bookmark->end >= $diff->start; - - if ( ! $update_head && ! $update_tail ) { + if ( $bookmark->start < $diff->start && $bookmark->end < $diff->start ) { break; } + if ( $bookmark->start >= $diff->start && $bookmark->end < $diff->end ) { + $this->release_bookmark( $bookmark_name ); + continue 2; + } + $delta = strlen( $diff->text ) - ( $diff->end - $diff->start ); - if ( $update_head ) { + if ( $bookmark->start >= $diff->start ) { $head_delta += $delta; } - if ( $update_tail ) { + if ( $bookmark->end >= $diff->end ) { $tail_delta += $delta; } } @@ -1465,6 +1516,20 @@ private function apply_attributes_updates() { } $this->lexical_updates = array(); + + return $accumulated_shift_for_given_point; + } + + /** + * Checks whether a bookmark with the given name exists. + * + * @since 6.3.0 + * + * @param string $bookmark_name Name to identify a bookmark that potentially exists. + * @return bool Whether that bookmark exists. + */ + public function has_bookmark( $bookmark_name ) { + return array_key_exists( $bookmark_name, $this->bookmarks ); } /** @@ -1502,8 +1567,6 @@ public function seek( $bookmark_name ) { // Point this tag processor before the sought tag opener and consume it. $this->bytes_already_parsed = $this->bookmarks[ $bookmark_name ]->start; - $this->bytes_already_copied = $this->bytes_already_parsed; - $this->output_buffer = substr( $this->html, 0, $this->bytes_already_copied ); return $this->next_tag( array( 'tag_closers' => 'visit' ) ); } @@ -1602,14 +1665,14 @@ private function get_enqueued_attribute_value( $comparable_name ) { * * Example: * ```php - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag( array( 'class_name' => 'test' ) ) === true; - * $p->get_attribute( 'data-test-id' ) === '14'; - * $p->get_attribute( 'enabled' ) === true; - * $p->get_attribute( 'aria-label' ) === null; - * - * $p->next_tag( array() ) === false; - * $p->get_attribute( 'class' ) === null; + * $p = new WP_HTML_Tag_Processor( '
Test
' ); + * $p->next_tag( array( 'class_name' => 'test' ) ) === true; + * $p->get_attribute( 'data-test-id' ) === '14'; + * $p->get_attribute( 'enabled' ) === true; + * $p->get_attribute( 'aria-label' ) === null; + * + * $p->next_tag() === false; + * $p->get_attribute( 'class' ) === null; * ``` * * @since 6.2.0 @@ -1681,20 +1744,20 @@ public function get_attribute( $name ) { * > case-insensitive match for each other. * - HTML 5 spec * - * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive - * * Example: * ```php - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag( array( 'class_name' => 'test' ) ) === true; - * $p->get_attribute_names_with_prefix( 'data-' ) === array( 'data-enabled', 'data-test-id' ); + * $p = new WP_HTML_Tag_Processor( '
Test
' ); + * $p->next_tag( array( 'class_name' => 'test' ) ) === true; + * $p->get_attribute_names_with_prefix( 'data-' ) === array( 'data-enabled', 'data-test-id' ); * - * $p->next_tag( array() ) === false; - * $p->get_attribute_names_with_prefix( 'data-' ) === null; + * $p->next_tag() === false; + * $p->get_attribute_names_with_prefix( 'data-' ) === null; * ``` * * @since 6.2.0 * + * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive + * * @param string $prefix Prefix of requested attribute names. * @return array|null List of attribute names, or `null` when no tag opener is matched. */ @@ -1719,12 +1782,12 @@ function get_attribute_names_with_prefix( $prefix ) { * * Example: * ```php - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag( array() ) === true; - * $p->get_tag() === 'DIV'; + * $p = new WP_HTML_Tag_Processor( '
Test
' ); + * $p->next_tag() === true; + * $p->get_tag() === 'DIV'; * - * $p->next_tag( array() ) === false; - * $p->get_tag() === null; + * $p->next_tag() === false; + * $p->get_tag() === null; * ``` * * @since 6.2.0 @@ -1741,17 +1804,42 @@ public function get_tag() { return strtoupper( $tag_name ); } + /** + * Indicates if the currently matched tag contains the self-closing flag. + * + * No HTML elements ought to have the self-closing flag and for those, the self-closing + * flag will be ignored. For void elements this is benign because they "self close" + * automatically. For non-void HTML elements though problems will appear if someone + * intends to use a self-closing element in place of that element with an empty body. + * For HTML foreign elements and custom elements the self-closing flag determines if + * they self-close or not. + * + * This function does not determine if a tag is self-closing, + * but only if the self-closing flag is present in the syntax. + * + * @since 6.3.0 + * + * @return bool Whether the currently matched tag contains the self-closing flag. + */ + public function has_self_closing_flag() { + if ( ! $this->tag_name_starts_at ) { + return false; + } + + return '/' === $this->html[ $this->tag_ends_at - 1 ]; + } + /** * Indicates if the current tag token is a tag closer. * * Example: * ```php - * $p = new WP_HTML_Tag_Processor( '
' ); - * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); - * $p->is_tag_closer() === false; + * $p = new WP_HTML_Tag_Processor( '
' ); + * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); + * $p->is_tag_closer() === false; * - * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); - * $p->is_tag_closer() === true; + * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); + * $p->is_tag_closer() === true; * ``` * * @since 6.2.0 @@ -1772,6 +1860,7 @@ public function is_tag_closer() { * For string attributes, the value is escaped using the `esc_attr` function. * * @since 6.2.0 + * @since 6.2.1 Fix: Only create a single update for multiple calls with case-variant attribute names. * * @param string $name The attribute name to target. * @param string|bool $value The new attribute value. @@ -1864,8 +1953,8 @@ public function set_attribute( $name, $value ) { * * Result:
*/ - $existing_attribute = $this->attributes[ $comparable_name ]; - $this->lexical_updates[ $name ] = new WP_HTML_Text_Replacement( + $existing_attribute = $this->attributes[ $comparable_name ]; + $this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement( $existing_attribute->start, $existing_attribute->end, $updated_attribute @@ -2010,7 +2099,8 @@ public function remove_class( $class_name ) { * Returns the string representation of the HTML Tag Processor. * * @since 6.2.0 - * @see get_updated_html + * + * @see WP_HTML_Tag_Processor::get_updated_html() * * @return string The processed HTML. */ @@ -2022,6 +2112,7 @@ public function __toString() { * Returns the string representation of the HTML Tag Processor. * * @since 6.2.0 + * @since 6.2.1 Shifts the internal cursor corresponding to the applied updates. * * @return string The processed HTML. */ @@ -2032,44 +2123,24 @@ public function get_updated_html() { * When there is nothing more to update and nothing has already been * updated, return the original document and avoid a string copy. */ - if ( $requires_no_updating && 0 === $this->bytes_already_copied ) { + if ( $requires_no_updating ) { return $this->html; } /* - * If there are no updates left to apply, but some have already - * been applied, then finish by copying the rest of the input - * to the end of the updated document and return. + * Keep track of the position right before the current tag. This will + * be necessary for reparsing the current tag after updating the HTML. */ - if ( $requires_no_updating && $this->bytes_already_copied > 0 ) { - return $this->output_buffer . substr( $this->html, $this->bytes_already_copied ); - } - - // Apply the updates, rewind to before the current tag, and reparse the attributes. - $content_up_to_opened_tag_name = $this->output_buffer . substr( - $this->html, - $this->bytes_already_copied, - $this->tag_name_starts_at + $this->tag_name_length - $this->bytes_already_copied - ); + $before_current_tag = $this->tag_name_starts_at - 1; /* - * 1. Apply the edits by flushing them to the output buffer and updating the copied byte count. - * - * Note: `apply_attributes_updates()` modifies `$this->output_buffer`. + * 1. Apply the enqueued edits and update all the pointers to reflect those changes. */ $this->class_name_updates_to_attributes_updates(); - $this->apply_attributes_updates(); + $before_current_tag += $this->apply_attributes_updates( $before_current_tag ); /* - * 2. Replace the original HTML with the now-updated HTML so that it's possible to - * seek to a previous location and have a consistent view of the updated document. - */ - $this->html = $this->output_buffer . substr( $this->html, $this->bytes_already_copied ); - $this->output_buffer = $content_up_to_opened_tag_name; - $this->bytes_already_copied = strlen( $this->output_buffer ); - - /* - * 3. Point this tag processor at the original tag opener and consume it + * 2. Rewind to before the current tag and reparse to get updated attributes. * * At this point the internal cursor points to the end of the tag name. * Rewind before the tag name starts so that it's as if the cursor didn't @@ -2081,9 +2152,19 @@ public function get_updated_html() { * ^ | back up by the length of the tag name plus the opening < * \<-/ back up by strlen("em") + 1 ==> 3 */ - $this->bytes_already_parsed = strlen( $content_up_to_opened_tag_name ) - $this->tag_name_length - 1; + + // Store existing state so it can be restored after reparsing. + $previous_parsed_byte_count = $this->bytes_already_parsed; + $previous_query = $this->last_query; + + // Reparse attributes. + $this->bytes_already_parsed = $before_current_tag; $this->next_tag(); + // Restore previous state. + $this->bytes_already_parsed = $previous_parsed_byte_count; + $this->parse_query( $previous_query ); + return $this->html; } diff --git a/lib/compat/wordpress-6.2/rest-api.php b/lib/compat/wordpress-6.2/rest-api.php index a504be4dca2a6b..58597e16d99282 100644 --- a/lib/compat/wordpress-6.2/rest-api.php +++ b/lib/compat/wordpress-6.2/rest-api.php @@ -84,15 +84,6 @@ function gutenberg_pattern_directory_collection_params_6_2( $query_params ) { } add_filter( 'rest_pattern_directory_collection_params', 'gutenberg_pattern_directory_collection_params_6_2' ); -/** - * Registers the Global Styles REST API routes. - */ -function gutenberg_register_global_styles_endpoints() { - $editor_settings = new Gutenberg_REST_Global_Styles_Controller_6_2(); - $editor_settings->register_routes(); -} -add_action( 'rest_api_init', 'gutenberg_register_global_styles_endpoints' ); - /** * Updates REST API response for the sidebars and marks them as 'inactive'. * diff --git a/lib/compat/wordpress-6.2/script-loader.php b/lib/compat/wordpress-6.2/script-loader.php index 149a6a18e14507..6ed69244726d33 100644 --- a/lib/compat/wordpress-6.2/script-loader.php +++ b/lib/compat/wordpress-6.2/script-loader.php @@ -129,28 +129,11 @@ function gutenberg_resolve_assets_override() { $scripts = ob_get_clean(); - /* - * Generate font @font-face styles for the site editor iframe. - * Use the registered font families for printing. - */ - if ( class_exists( 'WP_Fonts' ) ) { - $wp_fonts = wp_fonts(); - $registered = $wp_fonts->get_registered_font_families(); - if ( ! empty( $registered ) ) { - $queue = $wp_fonts->queue; - $done = $wp_fonts->done; - - $wp_fonts->done = array(); - $wp_fonts->queue = $registered; - - ob_start(); - $wp_fonts->do_items(); - $styles .= ob_get_clean(); - - // Reset the Web Fonts API. - $wp_fonts->done = $done; - $wp_fonts->queue = $queue; - } + // Generate font @font-face styles. + if ( function_exists( 'wp_print_fonts' ) ) { + ob_start(); + wp_print_fonts( true ); + $styles .= ob_get_clean(); } return array( @@ -169,25 +152,3 @@ function( $settings ) { }, 100 ); - -/** - * Enqueues the global styles custom css. - * - * @since 6.2.0 - */ -function gutenberg_enqueue_global_styles_custom_css() { - if ( ! wp_is_block_theme() ) { - return; - } - - // Don't enqueue Customizer's custom CSS separately. - remove_action( 'wp_head', 'wp_custom_css_cb', 101 ); - - $custom_css = wp_get_custom_css(); - $custom_css .= gutenberg_get_global_styles_custom_css(); - - if ( ! empty( $custom_css ) ) { - wp_add_inline_style( 'global-styles', $custom_css ); - } -} -add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_global_styles_custom_css' ); diff --git a/lib/compat/wordpress-6.3/blocks.php b/lib/compat/wordpress-6.3/blocks.php new file mode 100644 index 00000000000000..5f017997f52b58 --- /dev/null +++ b/lib/compat/wordpress-6.3/blocks.php @@ -0,0 +1,28 @@ += 6.3. + * + * @see https://github.com/WordPress/gutenberg/pull/46496 + * + * @param array $settings Current block type settings. + * @param array $metadata Block metadata as read in via block.json. + * + * @return array Filtered block type settings. + */ +function gutenberg_add_selectors_property_to_block_type_settings( $settings, $metadata ) { + if ( ! isset( $settings['selectors'] ) && isset( $metadata['selectors'] ) ) { + $settings['selectors'] = $metadata['selectors']; + } + + return $settings; +} +add_filter( 'block_type_metadata_settings', 'gutenberg_add_selectors_property_to_block_type_settings', 10, 2 ); diff --git a/lib/compat/wordpress-6.3/class-gutenberg-rest-global-styles-controller-6-3.php b/lib/compat/wordpress-6.3/class-gutenberg-rest-global-styles-controller-6-3.php new file mode 100644 index 00000000000000..5eeb0a1014aed6 --- /dev/null +++ b/lib/compat/wordpress-6.3/class-gutenberg-rest-global-styles-controller-6-3.php @@ -0,0 +1,51 @@ +namespace, $this->rest_base ); + + $links = array( + 'self' => array( + 'href' => rest_url( trailingslashit( $base ) . $id ), + ), + ); + + if ( post_type_supports( $this->post_type, 'revisions' ) ) { + $revisions = wp_get_latest_revision_id_and_total_count( $id ); + $revisions_count = ! is_wp_error( $revisions ) ? $revisions['count'] : 0; + $revisions_base = sprintf( '/%s/%s/%d/revisions', $this->namespace, $this->rest_base, $id ); + $links['version-history'] = array( + 'href' => rest_url( $revisions_base ), + 'count' => $revisions_count, + ); + } + + return $links; + } +} diff --git a/lib/compat/wordpress-6.3/class-gutenberg-rest-templates-controller-6-3.php b/lib/compat/wordpress-6.3/class-gutenberg-rest-templates-controller-6-3.php index 2f9bd7a8cd9e42..cbe9b5242a248e 100644 --- a/lib/compat/wordpress-6.3/class-gutenberg-rest-templates-controller-6-3.php +++ b/lib/compat/wordpress-6.3/class-gutenberg-rest-templates-controller-6-3.php @@ -9,7 +9,7 @@ /** * Base Templates REST API Controller. */ -class Gutenberg_REST_Templates_Controller_6_3 extends Gutenberg_REST_Templates_Controller { +class Gutenberg_REST_Templates_Controller_6_3 extends WP_REST_Templates_Controller { /** * Registers the controllers routes. diff --git a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php new file mode 100644 index 00000000000000..009fa6253f79d4 --- /dev/null +++ b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php @@ -0,0 +1,114 @@ +selectors ); + + // Root Selector. + + // Calculated before returning as it can be used as fallback for + // feature selectors later on. + $root_selector = null; + + if ( $has_selectors && isset( $block_type->selectors['root'] ) ) { + // Use the selectors API if available. + $root_selector = $block_type->selectors['root']; + } elseif ( isset( $block_type->supports['__experimentalSelector'] ) && is_string( $block_type->supports['__experimentalSelector'] ) ) { + // Use the old experimental selector supports property if set. + $root_selector = $block_type->supports['__experimentalSelector']; + } else { + // If no root selector found, generate default block class selector. + $block_name = str_replace( '/', '-', str_replace( 'core/', '', $block_type->name ) ); + $root_selector = ".wp-block-{$block_name}"; + } + + // Return selector if it's the root target we are looking for. + if ( 'root' === $target ) { + return $root_selector; + } + + // If target is not `root` we have a feature or subfeature as the target. + // If the target is a string convert to an array. + if ( is_string( $target ) ) { + $target = explode( '.', $target ); + } + + // Feature Selectors ( May fallback to root selector ). + if ( 1 === count( $target ) ) { + $fallback_selector = $fallback ? $root_selector : null; + + // Prefer the selectors API if available. + if ( $has_selectors ) { + // Look for selector under `feature.root`. + $path = array_merge( $target, array( 'root' ) ); + $feature_selector = _wp_array_get( $block_type->selectors, $path, null ); + + if ( $feature_selector ) { + return $feature_selector; + } + + // Check if feature selector is set via shorthand. + $feature_selector = _wp_array_get( $block_type->selectors, $target, null ); + + return is_string( $feature_selector ) ? $feature_selector : $fallback_selector; + } + + // Try getting old experimental supports selector value. + $path = array_merge( $target, array( '__experimentalSelector' ) ); + $feature_selector = _wp_array_get( $block_type->supports, $path, null ); + + // Nothing to work with, provide fallback or null. + if ( null === $feature_selector ) { + return $fallback_selector; + } + + // Scope the feature selector by the block's root selector. + return WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $feature_selector ); + } + + // Subfeature selector + // This may fallback either to parent feature or root selector. + $subfeature_selector = null; + + // Use selectors API if available. + if ( $has_selectors ) { + $subfeature_selector = _wp_array_get( $block_type->selectors, $target, null ); + } + + // Only return if we have a subfeature selector. + if ( $subfeature_selector ) { + return $subfeature_selector; + } + + // To this point we don't have a subfeature selector. If a fallback + // has been requested, remove subfeature from target path and return + // results of a call for the parent feature's selector. + if ( $fallback ) { + return wp_get_block_css_selector( $block_type, $target[0], $fallback ); + } + + // We tried... + return null; + } +} diff --git a/lib/compat/wordpress-6.3/html-api/class-gutenberg-html-tag-processor-6-3.php b/lib/compat/wordpress-6.3/html-api/class-gutenberg-html-tag-processor-6-3.php index feebe3fae13c1e..5b38484f659acc 100644 --- a/lib/compat/wordpress-6.3/html-api/class-gutenberg-html-tag-processor-6-3.php +++ b/lib/compat/wordpress-6.3/html-api/class-gutenberg-html-tag-processor-6-3.php @@ -39,10 +39,10 @@ * * Example: * ```php - * $tags = new WP_HTML_Tag_Processor( $html ); - * if ( $tags->next_tag( array( 'tag_name' => 'option' ) ) ) { - * $tags->set_attribute( 'selected', true ); - * } + * $tags = new WP_HTML_Tag_Processor( $html ); + * if ( $tags->next_tag( 'option' ) ) { + * $tags->set_attribute( 'selected', true ); + * } * ``` * * ### Finding tags @@ -55,13 +55,14 @@ * * If you want to _find whatever the next tag is_: * ```php - * $tags->next_tag(); + * $tags->next_tag(); * ``` * - * | Goal | Query | - * |-----------------------------------------------------------|----------------------------------------------------------------------------| - * | Find any tag. | `$tags->next_tag();` | + * | Goal | Query | + * |-----------------------------------------------------------|---------------------------------------------------------------------------------| + * | Find any tag. | `$tags->next_tag();` | * | Find next image tag. | `$tags->next_tag( array( 'tag_name' => 'img' ) );` | + * | Find next image tag (without passing the array). | `$tags->next_tag( 'img' );` | * | Find next tag containing the `fullwidth` CSS class. | `$tags->next_tag( array( 'class_name' => 'fullwidth' ) );` | * | Find next image tag containing the `fullwidth` CSS class. | `$tags->next_tag( array( 'tag_name' => 'img', 'class_name' => 'fullwidth' ) );` | * @@ -87,17 +88,17 @@ * * Example: * ```php - * // Paint up to the first five DIV or SPAN tags marked with the "jazzy" style. - * $remaining_count = 5; - * while ( $remaining_count > 0 && $tags->next_tag() ) { - * if ( - * ( 'DIV' === $tags->get_tag() || 'SPAN' === $tags->get_tag() ) && - * 'jazzy' === $tags->get_attribute( 'data-style' ) - * ) { - * $tags->add_class( 'theme-style-everest-jazz' ); - * $remaining_count--; - * } + * // Paint up to the first five DIV or SPAN tags marked with the "jazzy" style. + * $remaining_count = 5; + * while ( $remaining_count > 0 && $tags->next_tag() ) { + * if ( + * ( 'DIV' === $tags->get_tag() || 'SPAN' === $tags->get_tag() ) && + * 'jazzy' === $tags->get_attribute( 'data-style' ) + * ) { + * $tags->add_class( 'theme-style-everest-jazz' ); + * $remaining_count--; * } + * } * ``` * * `get_attribute()` will return `null` if the attribute wasn't present @@ -116,10 +117,10 @@ * * Example: * ```php - * if ( $tags->next_tag( array( 'class' => 'wp-group-block' ) ) ) { - * $tags->set_attribute( 'title', 'This groups the contained content.' ); - * $tags->remove_attribute( 'data-test-id' ); - * } + * if ( $tags->next_tag( array( 'class' => 'wp-group-block' ) ) ) { + * $tags->set_attribute( 'title', 'This groups the contained content.' ); + * $tags->remove_attribute( 'data-test-id' ); + * } * ``` * * If `set_attribute()` is called for an existing attribute it will @@ -141,29 +142,29 @@ * * Example: * ```php - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); + * // from `Yippee!` + * // to `Yippee!` + * $tags->add_class( 'is-active' ); * - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); + * // from `Yippee!` + * // to `Yippee!` + * $tags->add_class( 'is-active' ); * - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); + * // from `Yippee!` + * // to `Yippee!` + * $tags->add_class( 'is-active' ); * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); + * // from `` + * // to ` + * $tags->remove_class( 'rugby' ); * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); + * // from `` + * // to ` + * $tags->remove_class( 'rugby' ); * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); + * // from `` + * // to ` + * $tags->remove_class( 'rugby' ); * ``` * * When class changes are enqueued but a direct change to `class` is made via @@ -184,24 +185,24 @@ * bookmark and update it frequently, such as within a loop. * * ```php - * $total_todos = 0; - * while ( $p->next_tag( array( 'tag_name' => 'UL', 'class_name' => 'todo' ) ) ) { - * $p->set_bookmark( 'list-start' ); - * while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) { - * if ( 'UL' === $p->get_tag() && $p->is_tag_closer() ) { - * $p->set_bookmark( 'list-end' ); - * $p->seek( 'list-start' ); - * $p->set_attribute( 'data-contained-todos', (string) $total_todos ); - * $total_todos = 0; - * $p->seek( 'list-end' ); - * break; - * } + * $total_todos = 0; + * while ( $p->next_tag( array( 'tag_name' => 'UL', 'class_name' => 'todo' ) ) ) { + * $p->set_bookmark( 'list-start' ); + * while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) { + * if ( 'UL' === $p->get_tag() && $p->is_tag_closer() ) { + * $p->set_bookmark( 'list-end' ); + * $p->seek( 'list-start' ); + * $p->set_attribute( 'data-contained-todos', (string) $total_todos ); + * $total_todos = 0; + * $p->seek( 'list-end' ); + * break; + * } * - * if ( 'LI' === $p->get_tag() && ! $p->is_tag_closer() ) { - * $total_todos++; - * } + * if ( 'LI' === $p->get_tag() && ! $p->is_tag_closer() ) { + * $total_todos++; * } * } + * } * ``` * * ## Design and limitations @@ -228,11 +229,11 @@ * The Tag Processor's design incorporates a "garbage-in-garbage-out" philosophy. * HTML5 specifies that certain invalid content be transformed into different forms * for display, such as removing null bytes from an input document and replacing - * invalid characters with the Unicode replacement character U+FFFD �. Where errors - * or transformations exist within the HTML5 specification, the Tag Processor leaves - * those invalid inputs untouched, passing them through to the final browser to handle. - * While this implies that certain operations will be non-spec-compliant, such as - * reading the value of an attribute with invalid content, it also preserves a + * invalid characters with the Unicode replacement character `U+FFFD` (visually "�"). + * Where errors or transformations exist within the HTML5 specification, the Tag Processor + * leaves those invalid inputs untouched, passing them through to the final browser + * to handle. While this implies that certain operations will be non-spec-compliant, + * such as reading the value of an attribute with invalid content, it also preserves a * simplicity and efficiency for handling those error cases. * * Most operations within the Tag Processor are designed to minimize the difference @@ -252,9 +253,10 @@ class Gutenberg_HTML_Tag_Processor_6_3 { * The maximum number of bookmarks allowed to exist at * any given time. * - * @see set_bookmark() * @since 6.2.0 * @var int + * + * @see WP_HTML_Tag_Processor::set_bookmark() */ const MAX_BOOKMARKS = 10; @@ -262,9 +264,10 @@ class Gutenberg_HTML_Tag_Processor_6_3 { * Maximum number of times seek() can be called. * Prevents accidental infinite loops. * - * @see seek() * @since 6.2.0 * @var int + * + * @see WP_HTML_Tag_Processor::seek() */ const MAX_SEEK_OPS = 1000; @@ -316,23 +319,6 @@ class Gutenberg_HTML_Tag_Processor_6_3 { */ private $stop_on_tag_closers; - /** - * Holds updated HTML as updates are applied. - * - * Updates and unmodified portions of the input document are - * appended to this value as they are applied. It will hold - * a copy of the updated document up until the point of the - * latest applied update. The fully-updated HTML document - * will comprise this value plus the part of the input document - * which follows that latest update. - * - * @see $bytes_already_copied - * - * @since 6.2.0 - * @var string - */ - private $output_buffer = ''; - /** * How many bytes from the original HTML document have been read and parsed. * @@ -345,23 +331,6 @@ class Gutenberg_HTML_Tag_Processor_6_3 { */ private $bytes_already_parsed = 0; - /** - * How many bytes from the input HTML document have already been - * copied into the output buffer. - * - * Lexical updates are enqueued and processed in batches. Prior - * to any given update in the input document, there might exist - * a span of HTML unaffected by any changes. This span ought to - * be copied verbatim into the output buffer before applying the - * following update. This value will point to the starting byte - * offset in the input document where that unaffected span of - * HTML starts. - * - * @since 6.2.0 - * @var int - */ - private $bytes_already_copied = 0; - /** * Byte offset in input document where current tag name starts. * @@ -420,23 +389,23 @@ class Gutenberg_HTML_Tag_Processor_6_3 { * * Example: * ```php - * // supposing the parser is working through this content - * // and stops after recognizing the `id` attribute - * //
- * // ^ parsing will continue from this point - * $this->attributes = array( - * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ) - * ); - * - * // when picking up parsing again, or when asking to find the - * // `class` attribute we will continue and add to this array - * $this->attributes = array( - * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ), - * 'class' => new WP_HTML_Attribute_Match( 'class', 'outline', 18, 32 ) - * ); - * - * // Note that only the `class` attribute value is stored in the index. - * // That's because it is the only value used by this class at the moment. + * // supposing the parser is working through this content + * // and stops after recognizing the `id` attribute + * //
+ * // ^ parsing will continue from this point + * $this->attributes = array( + * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ) + * ); + * + * // when picking up parsing again, or when asking to find the + * // `class` attribute we will continue and add to this array + * $this->attributes = array( + * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ), + * 'class' => new WP_HTML_Attribute_Match( 'class', 'outline', 18, 32 ) + * ); + * + * // Note that only the `class` attribute value is stored in the index. + * // That's because it is the only value used by this class at the moment. * ``` * * @since 6.2.0 @@ -457,12 +426,12 @@ class Gutenberg_HTML_Tag_Processor_6_3 { * * Example: * ```php - * // Add the `wp-block-group` class, remove the `wp-group` class. - * $classname_updates = array( - * // Indexed by a comparable class name - * 'wp-block-group' => WP_HTML_Tag_Processor::ADD_CLASS, - * 'wp-group' => WP_HTML_Tag_Processor::REMOVE_CLASS - * ); + * // Add the `wp-block-group` class, remove the `wp-group` class. + * $classname_updates = array( + * // Indexed by a comparable class name + * 'wp-block-group' => WP_HTML_Tag_Processor::ADD_CLASS, + * 'wp-group' => WP_HTML_Tag_Processor::REMOVE_CLASS + * ); * ``` * * @since 6.2.0 @@ -511,16 +480,16 @@ class Gutenberg_HTML_Tag_Processor_6_3 { * * Example: * ```php - * // Replace an attribute stored with a new value, indices - * // sourced from the lazily-parsed HTML recognizer. - * $start = $attributes['src']->start; - * $end = $attributes['src']->end; - * $modifications[] = new WP_HTML_Text_Replacement( $start, $end, $new_value ); - * - * // Correspondingly, something like this will appear in this array. - * $lexical_updates = array( - * WP_HTML_Text_Replacement( 14, 28, 'https://my-site.my-domain/wp-content/uploads/2014/08/kittens.jpg' ) - * ); + * // Replace an attribute stored with a new value, indices + * // sourced from the lazily-parsed HTML recognizer. + * $start = $attributes['src']->start; + * $end = $attributes['src']->end; + * $modifications[] = new WP_HTML_Text_Replacement( $start, $end, $new_value ); + * + * // Correspondingly, something like this will appear in this array. + * $lexical_updates = array( + * WP_HTML_Text_Replacement( 14, 28, 'https://my-site.my-domain/wp-content/uploads/2014/08/kittens.jpg' ) + * ); * ``` * * @since 6.2.0 @@ -531,9 +500,10 @@ class Gutenberg_HTML_Tag_Processor_6_3 { /** * Tracks and limits `seek()` calls to prevent accidental infinite loops. * - * @see seek * @since 6.2.0 * @var int + * + * @see WP_HTML_Tag_Processor::seek() */ protected $seek_count = 0; @@ -753,9 +723,10 @@ public function release_bookmark( $name ) { /** * Skips contents of title and textarea tags. * - * @see https://html.spec.whatwg.org/multipage/parsing.html#rcdata-state * @since 6.2.0 * + * @see https://html.spec.whatwg.org/multipage/parsing.html#rcdata-state + * * @param string $tag_name – the lowercase tag name which will close the RCDATA region. * @return bool Whether an end to the RCDATA region was found before the end of the document. */ @@ -775,7 +746,8 @@ private function skip_rcdata( $tag_name ) { return false; } - $at += 2; + $closer_potentially_starts_at = $at; + $at += 2; /* * Find a case-insensitive match to the tag name. @@ -818,7 +790,7 @@ private function skip_rcdata( $tag_name ) { } if ( '>' === $html[ $at ] || '/' === $html[ $at ] ) { - ++$this->bytes_already_parsed; + $this->bytes_already_parsed = $closer_potentially_starts_at; return true; } } @@ -887,7 +859,8 @@ private function skip_script_data() { } if ( '/' === $html[ $at ] ) { - $is_closing = true; + $closer_potentially_starts_at = $at - 1; + $is_closing = true; ++$at; } else { $is_closing = false; @@ -938,7 +911,7 @@ private function skip_script_data() { } if ( $is_closing ) { - $this->bytes_already_parsed = $at; + $this->bytes_already_parsed = $closer_potentially_starts_at; if ( $this->bytes_already_parsed >= $doc_length ) { return false; } @@ -948,7 +921,7 @@ private function skip_script_data() { } if ( '>' === $html[ $this->bytes_already_parsed ] ) { - ++$this->bytes_already_parsed; + $this->bytes_already_parsed = $closer_potentially_starts_at; return true; } } @@ -968,6 +941,7 @@ private function skip_script_data() { * closing `>`; these are left for other methods. * * @since 6.2.0 + * @since 6.2.1 Support abruptly-closed comments, invalid-tag-closer-comments, and empty elements. * * @return bool Whether a tag was found before the end of the document. */ @@ -1036,13 +1010,42 @@ private function parse_next_tag() { '-' === $html[ $at + 2 ] && '-' === $html[ $at + 3 ] ) { - $closer_at = strpos( $html, '-->', $at + 4 ); - if ( false === $closer_at ) { + $closer_at = $at + 4; + // If it's not possible to close the comment then there is nothing more to scan. + if ( strlen( $html ) <= $closer_at ) { return false; } - $at = $closer_at + 3; - continue; + // Abruptly-closed empty comments are a sequence of dashes followed by `>`. + $span_of_dashes = strspn( $html, '-', $closer_at ); + if ( '>' === $html[ $closer_at + $span_of_dashes ] ) { + $at = $closer_at + $span_of_dashes + 1; + continue; + } + + /* + * Comments may be closed by either a --> or an invalid --!>. + * The first occurrence closes the comment. + * + * See https://html.spec.whatwg.org/#parse-error-incorrectly-closed-comment + */ + $closer_at--; // Pre-increment inside condition below reduces risk of accidental infinite looping. + while ( ++$closer_at < strlen( $html ) ) { + $closer_at = strpos( $html, '--', $closer_at ); + if ( false === $closer_at ) { + return false; + } + + if ( $closer_at + 2 < strlen( $html ) && '>' === $html[ $closer_at + 2 ] ) { + $at = $closer_at + 3; + continue 2; + } + + if ( $closer_at + 3 < strlen( $html ) && '!' === $html[ $closer_at + 2 ] && '>' === $html[ $closer_at + 3 ] ) { + $at = $closer_at + 4; + continue 2; + } + } } /* @@ -1101,9 +1104,19 @@ private function parse_next_tag() { continue; } + /* + * is a missing end tag name, which is ignored. + * + * See https://html.spec.whatwg.org/#parse-error-missing-end-tag-name + */ + if ( '>' === $html[ $at + 1 ] ) { + $at++; + continue; + } + /* * - * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state + * See https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state */ if ( '?' === $html[ $at + 1 ] ) { $closer_at = strpos( $html, '>', $at + 2 ); @@ -1115,6 +1128,22 @@ private function parse_next_tag() { continue; } + /* + * If a non-alpha starts the tag name in a tag closer it's a comment. + * Find the first `>`, which closes the comment. + * + * See https://html.spec.whatwg.org/#parse-error-invalid-first-character-of-tag-name + */ + if ( $this->is_closing_tag ) { + $closer_at = strpos( $html, '>', $at + 3 ); + if ( false === $closer_at ) { + return false; + } + + $at = $closer_at + 1; + continue; + } + ++$at; } @@ -1244,8 +1273,7 @@ private function skip_whitespace() { * @return void */ private function after_tag() { - $this->class_name_updates_to_attributes_updates(); - $this->apply_attributes_updates(); + $this->get_updated_html(); $this->tag_name_starts_at = null; $this->tag_name_length = null; $this->tag_ends_at = null; @@ -1257,11 +1285,11 @@ private function after_tag() { * Converts class name updates into tag attributes updates * (they are accumulated in different data formats for performance). * - * @see $lexical_updates - * @see $classname_updates - * * @since 6.2.0 * + * @see WP_HTML_Tag_Processor::$lexical_updates + * @see WP_HTML_Tag_Processor::$classname_updates + * * @return void */ private function class_name_updates_to_attributes_updates() { @@ -1401,15 +1429,19 @@ private function class_name_updates_to_attributes_updates() { * Applies attribute updates to HTML document. * * @since 6.2.0 + * @since 6.2.1 Accumulates shift for internal cursor and passed pointer. * @since 6.3.0 Invalidate any bookmarks whose targets are overwritten. * - * @return void + * @param int $shift_this_point Accumulate and return shift for this position. + * @return int How many bytes the given pointer moved in response to the updates. */ - private function apply_attributes_updates() { + private function apply_attributes_updates( $shift_this_point = 0 ) { if ( ! count( $this->lexical_updates ) ) { - return; + return 0; } + $accumulated_shift_for_given_point = 0; + /* * Attribute updates can be enqueued in any order but updates * to the document must occur in lexical order; that is, each @@ -1422,12 +1454,28 @@ private function apply_attributes_updates() { */ usort( $this->lexical_updates, array( self::class, 'sort_start_ascending' ) ); + $bytes_already_copied = 0; + $output_buffer = ''; foreach ( $this->lexical_updates as $diff ) { - $this->output_buffer .= substr( $this->html, $this->bytes_already_copied, $diff->start - $this->bytes_already_copied ); - $this->output_buffer .= $diff->text; - $this->bytes_already_copied = $diff->end; + $shift = strlen( $diff->text ) - ( $diff->end - $diff->start ); + + // Adjust the cursor position by however much an update affects it. + if ( $diff->start <= $this->bytes_already_parsed ) { + $this->bytes_already_parsed += $shift; + } + + // Accumulate shift of the given pointer within this function call. + if ( $diff->start <= $shift_this_point ) { + $accumulated_shift_for_given_point += $shift; + } + + $output_buffer .= substr( $this->html, $bytes_already_copied, $diff->start - $bytes_already_copied ); + $output_buffer .= $diff->text; + $bytes_already_copied = $diff->end; } + $this->html = $output_buffer . substr( $this->html, $bytes_already_copied ); + /* * Adjust bookmark locations to account for how the text * replacements adjust offsets in the input document. @@ -1468,6 +1516,8 @@ private function apply_attributes_updates() { } $this->lexical_updates = array(); + + return $accumulated_shift_for_given_point; } /** @@ -1517,8 +1567,6 @@ public function seek( $bookmark_name ) { // Point this tag processor before the sought tag opener and consume it. $this->bytes_already_parsed = $this->bookmarks[ $bookmark_name ]->start; - $this->bytes_already_copied = $this->bytes_already_parsed; - $this->output_buffer = substr( $this->html, 0, $this->bytes_already_copied ); return $this->next_tag( array( 'tag_closers' => 'visit' ) ); } @@ -1617,14 +1665,14 @@ private function get_enqueued_attribute_value( $comparable_name ) { * * Example: * ```php - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag( array( 'class_name' => 'test' ) ) === true; - * $p->get_attribute( 'data-test-id' ) === '14'; - * $p->get_attribute( 'enabled' ) === true; - * $p->get_attribute( 'aria-label' ) === null; - * - * $p->next_tag( array() ) === false; - * $p->get_attribute( 'class' ) === null; + * $p = new WP_HTML_Tag_Processor( '
Test
' ); + * $p->next_tag( array( 'class_name' => 'test' ) ) === true; + * $p->get_attribute( 'data-test-id' ) === '14'; + * $p->get_attribute( 'enabled' ) === true; + * $p->get_attribute( 'aria-label' ) === null; + * + * $p->next_tag() === false; + * $p->get_attribute( 'class' ) === null; * ``` * * @since 6.2.0 @@ -1696,20 +1744,20 @@ public function get_attribute( $name ) { * > case-insensitive match for each other. * - HTML 5 spec * - * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive - * * Example: * ```php - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag( array( 'class_name' => 'test' ) ) === true; - * $p->get_attribute_names_with_prefix( 'data-' ) === array( 'data-enabled', 'data-test-id' ); + * $p = new WP_HTML_Tag_Processor( '
Test
' ); + * $p->next_tag( array( 'class_name' => 'test' ) ) === true; + * $p->get_attribute_names_with_prefix( 'data-' ) === array( 'data-enabled', 'data-test-id' ); * - * $p->next_tag( array() ) === false; - * $p->get_attribute_names_with_prefix( 'data-' ) === null; + * $p->next_tag() === false; + * $p->get_attribute_names_with_prefix( 'data-' ) === null; * ``` * * @since 6.2.0 * + * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive + * * @param string $prefix Prefix of requested attribute names. * @return array|null List of attribute names, or `null` when no tag opener is matched. */ @@ -1734,12 +1782,12 @@ function get_attribute_names_with_prefix( $prefix ) { * * Example: * ```php - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag( array() ) === true; - * $p->get_tag() === 'DIV'; + * $p = new WP_HTML_Tag_Processor( '
Test
' ); + * $p->next_tag() === true; + * $p->get_tag() === 'DIV'; * - * $p->next_tag( array() ) === false; - * $p->get_tag() === null; + * $p->next_tag() === false; + * $p->get_tag() === null; * ``` * * @since 6.2.0 @@ -1756,17 +1804,42 @@ public function get_tag() { return strtoupper( $tag_name ); } + /** + * Indicates if the currently matched tag contains the self-closing flag. + * + * No HTML elements ought to have the self-closing flag and for those, the self-closing + * flag will be ignored. For void elements this is benign because they "self close" + * automatically. For non-void HTML elements though problems will appear if someone + * intends to use a self-closing element in place of that element with an empty body. + * For HTML foreign elements and custom elements the self-closing flag determines if + * they self-close or not. + * + * This function does not determine if a tag is self-closing, + * but only if the self-closing flag is present in the syntax. + * + * @since 6.3.0 + * + * @return bool Whether the currently matched tag contains the self-closing flag. + */ + public function has_self_closing_flag() { + if ( ! $this->tag_name_starts_at ) { + return false; + } + + return '/' === $this->html[ $this->tag_ends_at - 1 ]; + } + /** * Indicates if the current tag token is a tag closer. * * Example: * ```php - * $p = new WP_HTML_Tag_Processor( '
' ); - * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); - * $p->is_tag_closer() === false; + * $p = new WP_HTML_Tag_Processor( '
' ); + * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); + * $p->is_tag_closer() === false; * - * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); - * $p->is_tag_closer() === true; + * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); + * $p->is_tag_closer() === true; * ``` * * @since 6.2.0 @@ -1787,6 +1860,7 @@ public function is_tag_closer() { * For string attributes, the value is escaped using the `esc_attr` function. * * @since 6.2.0 + * @since 6.2.1 Fix: Only create a single update for multiple calls with case-variant attribute names. * * @param string $name The attribute name to target. * @param string|bool $value The new attribute value. @@ -1879,8 +1953,8 @@ public function set_attribute( $name, $value ) { * * Result:
*/ - $existing_attribute = $this->attributes[ $comparable_name ]; - $this->lexical_updates[ $name ] = new WP_HTML_Text_Replacement( + $existing_attribute = $this->attributes[ $comparable_name ]; + $this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement( $existing_attribute->start, $existing_attribute->end, $updated_attribute @@ -2025,7 +2099,8 @@ public function remove_class( $class_name ) { * Returns the string representation of the HTML Tag Processor. * * @since 6.2.0 - * @see get_updated_html + * + * @see WP_HTML_Tag_Processor::get_updated_html() * * @return string The processed HTML. */ @@ -2037,6 +2112,7 @@ public function __toString() { * Returns the string representation of the HTML Tag Processor. * * @since 6.2.0 + * @since 6.2.1 Shifts the internal cursor corresponding to the applied updates. * * @return string The processed HTML. */ @@ -2047,44 +2123,24 @@ public function get_updated_html() { * When there is nothing more to update and nothing has already been * updated, return the original document and avoid a string copy. */ - if ( $requires_no_updating && 0 === $this->bytes_already_copied ) { + if ( $requires_no_updating ) { return $this->html; } /* - * If there are no updates left to apply, but some have already - * been applied, then finish by copying the rest of the input - * to the end of the updated document and return. + * Keep track of the position right before the current tag. This will + * be necessary for reparsing the current tag after updating the HTML. */ - if ( $requires_no_updating && $this->bytes_already_copied > 0 ) { - return $this->output_buffer . substr( $this->html, $this->bytes_already_copied ); - } - - // Apply the updates, rewind to before the current tag, and reparse the attributes. - $content_up_to_opened_tag_name = $this->output_buffer . substr( - $this->html, - $this->bytes_already_copied, - $this->tag_name_starts_at + $this->tag_name_length - $this->bytes_already_copied - ); + $before_current_tag = $this->tag_name_starts_at - 1; /* - * 1. Apply the edits by flushing them to the output buffer and updating the copied byte count. - * - * Note: `apply_attributes_updates()` modifies `$this->output_buffer`. + * 1. Apply the enqueued edits and update all the pointers to reflect those changes. */ $this->class_name_updates_to_attributes_updates(); - $this->apply_attributes_updates(); + $before_current_tag += $this->apply_attributes_updates( $before_current_tag ); /* - * 2. Replace the original HTML with the now-updated HTML so that it's possible to - * seek to a previous location and have a consistent view of the updated document. - */ - $this->html = $this->output_buffer . substr( $this->html, $this->bytes_already_copied ); - $this->output_buffer = $content_up_to_opened_tag_name; - $this->bytes_already_copied = strlen( $this->output_buffer ); - - /* - * 3. Point this tag processor at the original tag opener and consume it + * 2. Rewind to before the current tag and reparse to get updated attributes. * * At this point the internal cursor points to the end of the tag name. * Rewind before the tag name starts so that it's as if the cursor didn't @@ -2096,9 +2152,19 @@ public function get_updated_html() { * ^ | back up by the length of the tag name plus the opening < * \<-/ back up by strlen("em") + 1 ==> 3 */ - $this->bytes_already_parsed = strlen( $content_up_to_opened_tag_name ) - $this->tag_name_length - 1; + + // Store existing state so it can be restored after reparsing. + $previous_parsed_byte_count = $this->bytes_already_parsed; + $previous_query = $this->last_query; + + // Reparse attributes. + $this->bytes_already_parsed = $before_current_tag; $this->next_tag(); + // Restore previous state. + $this->bytes_already_parsed = $previous_parsed_byte_count; + $this->parse_query( $previous_query ); + return $this->html; } diff --git a/lib/compat/wordpress-6.3/link-template.php b/lib/compat/wordpress-6.3/link-template.php new file mode 100644 index 00000000000000..366dedba2b5aaf --- /dev/null +++ b/lib/compat/wordpress-6.3/link-template.php @@ -0,0 +1,34 @@ +post_type || 'wp_template_part' === $post->post_type ) { + $post_type_object = get_post_type_object( $post->post_type ); + $slug = urlencode( get_stylesheet() . '//' . $post->post_name ); + $link = admin_url( sprintf( $post_type_object->_edit_link, $slug ) ); + } + return $link; +} + +add_filter( 'get_edit_post_link', 'gutenberg_update_get_edit_post_link', 10, 2 ); diff --git a/lib/compat/wordpress-6.3/navigation-block-preloading.php b/lib/compat/wordpress-6.3/navigation-block-preloading.php new file mode 100644 index 00000000000000..82fe81b236a590 --- /dev/null +++ b/lib/compat/wordpress-6.3/navigation-block-preloading.php @@ -0,0 +1,63 @@ +name ) && 'core/edit-site' !== $context->name ) { + return $preload_paths; + } + + $navigation_rest_route = rest_get_route_for_post_type_items( + 'wp_navigation' + ); + + // Preload the OPTIONS request for all Navigation posts request. + $preload_paths[] = array( $navigation_rest_route, 'OPTIONS' ); + + // Preload the GET request for ALL 'published' or 'draft' Navigation posts. + $preload_paths[] = array( + add_query_arg( + array( + 'context' => 'edit', + 'per_page' => 100, + '_locale' => 'user', + // array indices are required to avoid query being encoded and not matching in cache. + 'status[0]' => 'publish', + 'status[1]' => 'draft', + ), + $navigation_rest_route + ), + 'GET', + ); + + // Preload request for Browse Mode sidebar "Navigation" section. + $preload_paths[] = array( + add_query_arg( + array( + 'context' => 'edit', + 'per_page' => 1, + 'status' => 'publish', + 'order' => 'desc', + 'orderby' => 'date', + ), + $navigation_rest_route + ), + 'GET', + ); + + return $preload_paths; +} +add_filter( 'block_editor_rest_api_preload_paths', 'gutenberg_preload_navigation_posts', 10, 2 ); diff --git a/lib/compat/wordpress-6.3/rest-api.php b/lib/compat/wordpress-6.3/rest-api.php index 93422289100dfa..757bd317dcd548 100644 --- a/lib/compat/wordpress-6.3/rest-api.php +++ b/lib/compat/wordpress-6.3/rest-api.php @@ -15,16 +15,64 @@ function gutenberg_register_rest_pattern_directory() { add_action( 'rest_api_init', 'gutenberg_register_rest_pattern_directory' ); /** - * Update `wp_template` and `wp_template-part` post types to use - * Gutenberg's REST controller. + * Updates `wp_template` and `wp_template_part` post types to use + * Gutenberg's REST controllers + * + * Adds `_edit_link` to the `wp_global_styles`, `wp_template`, + * and `wp_template_part` post type schemata. See https://github.com/WordPress/gutenberg/issues/48065 * * @param array $args Array of arguments for registering a post type. * @param string $post_type Post type key. */ function gutenberg_update_templates_template_parts_rest_controller( $args, $post_type ) { - if ( in_array( $post_type, array( 'wp_template', 'wp_template-part' ), true ) ) { + if ( in_array( $post_type, array( 'wp_template', 'wp_template_part' ), true ) ) { + $template_edit_link = 'site-editor.php?' . build_query( + array( + 'postType' => $post_type, + 'postId' => '%s', + 'canvas' => 'edit', + ) + ); + $args['_edit_link'] = $template_edit_link; $args['rest_controller_class'] = 'Gutenberg_REST_Templates_Controller_6_3'; } + + if ( in_array( $post_type, array( 'wp_global_styles' ), true ) ) { + $args['_edit_link'] = '/site-editor.php?canvas=edit'; + } return $args; } add_filter( 'register_post_type_args', 'gutenberg_update_templates_template_parts_rest_controller', 10, 2 ); + +/** + * Registers the Global Styles Revisions REST API routes. + */ +function gutenberg_register_global_styles_revisions_endpoints() { + $global_styles_revisions_controller = new Gutenberg_REST_Global_Styles_Revisions_Controller(); + $global_styles_revisions_controller->register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_global_styles_revisions_endpoints' ); + +/** + * Registers the Global Styles REST API routes. + */ +function gutenberg_register_global_styles_endpoints() { + $global_styles_controller = new Gutenberg_REST_Global_Styles_Controller_6_3(); + $global_styles_controller->register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_global_styles_endpoints' ); + +/** + * Update `wp_global_styles` post type to use Gutenberg's REST controller. + * + * @param array $args Array of arguments for registering a post type. + * @param string $post_type Post type key. + */ +function gutenberg_update_global_styles_rest_controller( $args, $post_type ) { + if ( in_array( $post_type, array( 'wp_global_styles' ), true ) ) { + $args['rest_controller_class'] = 'Gutenberg_REST_Templates_Controller_6_3'; + $args['rest_base'] = 'global-styles'; + } + return $args; +} +add_filter( 'register_post_type_args', 'gutenberg_update_global_styles_rest_controller', 10, 2 ); diff --git a/lib/compat/wordpress-6.3/script-loader.php b/lib/compat/wordpress-6.3/script-loader.php new file mode 100644 index 00000000000000..8b00e10d09b660 --- /dev/null +++ b/lib/compat/wordpress-6.3/script-loader.php @@ -0,0 +1,85 @@ +registered = $current_wp_styles->registered; + $wp_scripts->registered = $current_wp_scripts->registered; + + wp_enqueue_style( 'wp-block-editor-content' ); + // To do: investigate why this is not enqueued through enqueue_block_assets, + // as styles for non-core blocks are. + wp_enqueue_style( 'wp-block-library' ); + wp_enqueue_script( 'wp-polyfill' ); + + // We don't want to load EDITOR scripts and styles in the iframe, only + // assets for the content. + add_filter( 'should_load_block_editor_scripts_and_styles', '__return_false' ); + do_action( 'enqueue_block_assets' ); + remove_filter( 'should_load_block_editor_scripts_and_styles', '__return_false' ); + + ob_start(); + wp_print_styles(); + wp_print_fonts( true ); + $styles = ob_get_clean(); + + ob_start(); + wp_print_head_scripts(); + wp_print_footer_scripts(); + $scripts = ob_get_clean(); + + // Restore the original instances. + $wp_styles = $current_wp_styles; + $wp_scripts = $current_wp_scripts; + + return array( + 'styles' => $styles, + 'scripts' => $scripts, + ); +} + +add_filter( + 'block_editor_settings_all', + function( $settings ) { + // We must override what core is passing now. + $settings['__unstableResolvedAssets'] = _gutenberg_get_iframed_editor_assets(); + return $settings; + }, + 100 +); diff --git a/lib/compat/wordpress-6.3/theme-previews.php b/lib/compat/wordpress-6.3/theme-previews.php new file mode 100644 index 00000000000000..e73c13daa7cfb5 --- /dev/null +++ b/lib/compat/wordpress-6.3/theme-previews.php @@ -0,0 +1,129 @@ +errors() ) ) { + if ( current_filter() === 'template' ) { + $theme_path = $wp_theme->get_template(); + } else { + $theme_path = $wp_theme->get_stylesheet(); + } + + return sanitize_text_field( $theme_path ); + } + + return $current_stylesheet; +} + +/** + * Adds a middleware to the REST API to set the theme for the preview. + */ +function gutenberg_attach_theme_preview_middleware() { + // Don't allow non-admins to preview themes. + if ( ! current_user_can( 'switch_themes' ) ) { + return; + } + + wp_add_inline_script( + 'wp-api-fetch', + sprintf( + 'wp.apiFetch.use( wp.apiFetch.createThemePreviewMiddleware( %s ) );', + wp_json_encode( sanitize_text_field( $_GET['theme_preview'] ) ) + ), + 'after' + ); +} + +/** + * Temporary function to add a live preview button to block themes. + * Remove when https://core.trac.wordpress.org/ticket/58190 lands. + */ +function add_live_preview_button() { + global $pagenow; + if ( 'themes.php' === $pagenow ) { + ?> + + + + slug ) { + $page_slug = 'page'; + } + if ( 'single' === $template_type->slug ) { + $post_slug = 'single'; + } + } + + $what_post_type = get_post_type( $post_ID ); + switch ( $what_post_type ) { + case 'page': + $template_slug = $page_slug; + break; + default: + $template_slug = $post_slug; + break; + } + } + + $current_template = get_block_templates( array( 'slug__in' => array( $template_slug ) ) ); + + if ( ! empty( $current_template ) ) { + $template_blocks = parse_blocks( $current_template[0]->content ); + $post_content_block = gutenberg_find_first_block( 'core/post-content', $template_blocks ); + + if ( ! empty( $post_content_block['attrs'] ) ) { + $settings['postContentAttributes'] = $post_content_block['attrs']; + } + } + + return $settings; +} + +add_filter( 'block_editor_settings_all', 'gutenberg_get_block_editor_settings_experimental', PHP_INT_MAX ); diff --git a/lib/experimental/class-gutenberg-rest-global-styles-revisions-controller.php b/lib/experimental/class-gutenberg-rest-global-styles-revisions-controller.php new file mode 100644 index 00000000000000..c5d5a5f0dff1a4 --- /dev/null +++ b/lib/experimental/class-gutenberg-rest-global-styles-revisions-controller.php @@ -0,0 +1,311 @@ +parent_post_type = 'wp_global_styles'; + $this->rest_base = 'revisions'; + $this->parent_base = 'global-styles'; + $this->namespace = 'wp/v2'; + } + + /** + * Registers the controllers routes. + * + * @return void + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base, + array( + 'args' => array( + 'parent' => array( + 'description' => __( 'The ID for the parent of the revision.', 'gutenberg' ), + 'type' => 'integer', + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + } + + /** + * Returns revisions of the given global styles config custom post type. + * + * @since 6.3.0 + * + * @param WP_REST_Request $request The request instance. + * + * @return WP_REST_Response|WP_Error + */ + public function get_items( $request ) { + $parent = $this->get_parent( $request['parent'] ); + + if ( is_wp_error( $parent ) ) { + return $parent; + } + $response = array(); + $raw_config = json_decode( $parent->post_content, true ); + $is_global_styles_user_theme_json = isset( $raw_config['isGlobalStylesUserThemeJSON'] ) && true === $raw_config['isGlobalStylesUserThemeJSON']; + + if ( $is_global_styles_user_theme_json ) { + $user_theme_revisions = wp_get_post_revisions( + $parent->ID, + array( + 'posts_per_page' => 100, + ) + ); + + if ( ! empty( $user_theme_revisions ) ) { + foreach ( $user_theme_revisions as $revision ) { + $revision = $this->prepare_item_for_response( $revision, $request ); + $response[] = $this->prepare_response_for_collection( $revision ); + } + } + } + + return rest_ensure_response( $response ); + } + + /** + * Prepares the revision for the REST response. + * + * @since 6.3.0 + * + * @param WP_Post $item Post revision object. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response Response object. + */ + public function prepare_item_for_response( $item, $request ) { + $parent = $this->get_parent( $request['parent'] ); + // Retrieves global styles config as JSON. + $raw_revision_config = json_decode( $item->post_content, true ); + $config = ( new WP_Theme_JSON_Gutenberg( $raw_revision_config, 'custom' ) )->get_raw_data(); + + // Prepares item data. + $data = array(); + $fields = $this->get_fields_for_response( $request ); + + if ( rest_is_field_included( 'author', $fields ) ) { + $data['author'] = (int) $item->post_author; + } + + if ( rest_is_field_included( 'date', $fields ) ) { + $data['date'] = $item->post_date; + } + + if ( rest_is_field_included( 'date_gmt', $fields ) ) { + $data['date_gmt'] = $item->post_date_gmt; + } + + if ( rest_is_field_included( 'id', $fields ) ) { + $data['id'] = (int) $item->ID; + } + + if ( rest_is_field_included( 'modified', $fields ) ) { + $data['modified'] = $item->post_modified; + } + + if ( rest_is_field_included( 'modified_gmt', $fields ) ) { + $data['modified_gmt'] = $item->post_modified_gmt; + } + + if ( rest_is_field_included( 'parent', $fields ) ) { + $data['parent'] = (int) $parent->ID; + } + + if ( rest_is_field_included( 'settings', $fields ) ) { + $data['settings'] = ! empty( $config['settings'] ) ? $config['settings'] : new stdClass(); + } + + if ( rest_is_field_included( 'styles', $fields ) ) { + $data['styles'] = ! empty( $config['styles'] ) ? $config['styles'] : new stdClass(); + } + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + return rest_ensure_response( $data ); + } + + /** + * Retrieves the revision's schema, conforming to JSON Schema. + * + * @since 6.3.0 + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => "{$this->parent_post_type}-revision", + 'type' => 'object', + // Base properties for every Revision. + 'properties' => array( + + /* + * Adds settings and styles from the WP_REST_Revisions_Controller item fields. + * Leaves out GUID as global styles shouldn't be accessible via URL. + */ + 'author' => array( + 'description' => __( 'The ID for the author of the revision.', 'gutenberg' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'date' => array( + 'description' => __( "The date the revision was published, in the site's timezone.", 'gutenberg' ), + 'type' => 'string', + 'format' => 'date-time', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'date_gmt' => array( + 'description' => __( 'The date the revision was published, as GMT.', 'gutenberg' ), + 'type' => 'string', + 'format' => 'date-time', + 'context' => array( 'view', 'edit' ), + ), + 'id' => array( + 'description' => __( 'Unique identifier for the revision.', 'gutenberg' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'modified' => array( + 'description' => __( "The date the revision was last modified, in the site's timezone.", 'gutenberg' ), + 'type' => 'string', + 'format' => 'date-time', + 'context' => array( 'view', 'edit' ), + ), + 'modified_gmt' => array( + 'description' => __( 'The date the revision was last modified, as GMT.', 'gutenberg' ), + 'type' => 'string', + 'format' => 'date-time', + 'context' => array( 'view', 'edit' ), + ), + 'parent' => array( + 'description' => __( 'The ID for the parent of the revision.', 'gutenberg' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + ), + + // Adds settings and styles from the WP_REST_Global_Styles_Controller parent schema. + 'styles' => array( + 'description' => __( 'Global styles.', 'gutenberg' ), + 'type' => array( 'object' ), + 'context' => array( 'view', 'edit' ), + ), + 'settings' => array( + 'description' => __( 'Global settings.', 'gutenberg' ), + 'type' => array( 'object' ), + 'context' => array( 'view', 'edit' ), + ), + ), + ); + + $this->schema = $schema; + + return $this->add_additional_fields_schema( $this->schema ); + } + + /** + * Checks if a given request has access to read a single global style. + * + * @since 6.3.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + */ + public function get_item_permissions_check( $request ) { + $post = $this->get_parent( $request['parent'] ); + if ( is_wp_error( $post ) ) { + return $post; + } + + /* + * The same check as WP_REST_Global_Styles_Controller->get_item_permissions_check. + */ + if ( ! current_user_can( 'read_post', $post->ID ) ) { + return new WP_Error( + 'rest_cannot_view', + __( 'Sorry, you are not allowed to view revisions for this global style.', 'gutenberg' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + return true; + } + + /** + * Get the parent post, if the ID is valid. Copied from WP_REST_Revisions_Controller. + * + * @since 6.3.0 + * + * @param int $parent_post_id Supplied ID. + * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. + */ + protected function get_parent( $parent_post_id ) { + $error = new WP_Error( + 'rest_post_invalid_parent', + __( 'Invalid post parent ID.', 'gutenberg' ), + array( 'status' => 404 ) + ); + + if ( (int) $parent_post_id <= 0 ) { + return $error; + } + + $parent_post = get_post( (int) $parent_post_id ); + + if ( empty( $parent_post ) || empty( $parent_post->ID ) + || $this->parent_post_type !== $parent_post->post_type + ) { + return $error; + } + + return $parent_post; + } +} diff --git a/lib/experimental/class-wp-classic-to-block-menu-converter.php b/lib/experimental/class-wp-classic-to-block-menu-converter.php new file mode 100644 index 00000000000000..77232bee2ac8c8 --- /dev/null +++ b/lib/experimental/class-wp-classic-to-block-menu-converter.php @@ -0,0 +1,111 @@ +term_id, array( 'update_post_term_cache' => false ) ); + + // Set up the $menu_item variables. + // Adds the class property classes for the current context, if applicable. + _wp_menu_item_classes_by_context( $menu_items ); + + $menu_items_by_parent_id = static::group_by_parent_id( $menu_items ); + + $first_menu_item = isset( $menu_items_by_parent_id[0] ) + ? $menu_items_by_parent_id[0] + : array(); + + $inner_blocks = static::to_blocks( + $first_menu_item, + $menu_items_by_parent_id + ); + + return serialize_blocks( $inner_blocks ); + } + + /** + * Returns an array of menu items grouped by the id of the parent menu item. + * + * @param array $menu_items An array of menu items. + * @return array + */ + private static function group_by_parent_id( $menu_items ) { + $menu_items_by_parent_id = array(); + + foreach ( $menu_items as $menu_item ) { + $menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item; + } + + return $menu_items_by_parent_id; + } + + /** + * Turns menu item data into a nested array of parsed blocks + * + * @param array $menu_items An array of menu items that represent + * an individual level of a menu. + * @param array $menu_items_by_parent_id An array keyed by the id of the + * parent menu where each element is an + * array of menu items that belong to + * that parent. + * @return array An array of parsed block data. + */ + private static function to_blocks( $menu_items, $menu_items_by_parent_id ) { + + if ( empty( $menu_items ) ) { + return array(); + } + + $blocks = array(); + + foreach ( $menu_items as $menu_item ) { + $class_name = ! empty( $menu_item->classes ) ? implode( ' ', (array) $menu_item->classes ) : null; + $id = ( null !== $menu_item->object_id && 'custom' !== $menu_item->object ) ? $menu_item->object_id : null; + $opens_in_new_tab = null !== $menu_item->target && '_blank' === $menu_item->target; + $rel = ( null !== $menu_item->xfn && '' !== $menu_item->xfn ) ? $menu_item->xfn : null; + $kind = null !== $menu_item->type ? str_replace( '_', '-', $menu_item->type ) : 'custom'; + + $block = array( + 'blockName' => isset( $menu_items_by_parent_id[ $menu_item->ID ] ) ? 'core/navigation-submenu' : 'core/navigation-link', + 'attrs' => array( + 'className' => $class_name, + 'description' => $menu_item->description, + 'id' => $id, + 'kind' => $kind, + 'label' => $menu_item->title, + 'opensInNewTab' => $opens_in_new_tab, + 'rel' => $rel, + 'title' => $menu_item->attr_title, + 'type' => $menu_item->object, + 'url' => $menu_item->url, + ), + ); + + $block['innerBlocks'] = isset( $menu_items_by_parent_id[ $menu_item->ID ] ) + ? static::to_blocks( $menu_items_by_parent_id[ $menu_item->ID ], $menu_items_by_parent_id ) + : array(); + $block['innerContent'] = array_map( 'serialize_block', $block['innerBlocks'] ); + + $blocks[] = $block; + } + + return $blocks; + } +} diff --git a/lib/experimental/class-wp-navigation-fallback-gutenberg.php b/lib/experimental/class-wp-navigation-fallback-gutenberg.php new file mode 100644 index 00000000000000..0e81a36bb68cd4 --- /dev/null +++ b/lib/experimental/class-wp-navigation-fallback-gutenberg.php @@ -0,0 +1,233 @@ + 'wp_navigation', + 'no_found_rows' => true, + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'order' => 'DESC', + 'orderby' => 'date', + 'post_status' => 'publish', + 'posts_per_page' => 1, + ); + + $navigation_post = new WP_Query( $parsed_args ); + + if ( count( $navigation_post->posts ) > 0 ) { + return $navigation_post->posts[0]; + } + + return null; + } + + /** + * Creates a Navigation Menu post from a Classic Menu. + * + * @return int|WP_Error The post ID of the default fallback menu or a WP_Error object. + */ + private static function create_classic_menu_fallback() { + // See if we have a classic menu. + $classic_nav_menu = static::get_fallback_classic_menu(); + + if ( ! $classic_nav_menu ) { + return new WP_Error( 'no_classic_menus', __( 'No Classic Menus found.', 'gutenberg' ) ); + } + + // If there is a classic menu then convert it to blocks. + $classic_nav_menu_blocks = WP_Classic_To_Block_Menu_Converter::convert( $classic_nav_menu ); + + if ( empty( $classic_nav_menu_blocks ) ) { + return new WP_Error( 'cannot_convert_classic_menu', __( 'Unable to convert Classic Menu to blocks.', 'gutenberg' ) ); + } + + // Create a new navigation menu from the classic menu. + $classic_menu_fallback = wp_insert_post( + array( + 'post_content' => $classic_nav_menu_blocks, + 'post_title' => $classic_nav_menu->name, + 'post_name' => $classic_nav_menu->slug, + 'post_status' => 'publish', + 'post_type' => 'wp_navigation', + ), + true // So that we can check whether the result is an error. + ); + + return $classic_menu_fallback; + } + + /** + * Determine the most appropriate classic navigation menu to use as a fallback. + * + * @return WP_Term|null The most appropriate classic navigation menu to use as a fallback. + */ + private static function get_fallback_classic_menu() { + $classic_nav_menus = wp_get_nav_menus(); + + if ( ! $classic_nav_menus || is_wp_error( $classic_nav_menus ) ) { + return null; + } + + $nav_menu = static::get_nav_menu_at_primary_location(); + + if ( $nav_menu ) { + return $nav_menu; + } + + $nav_menu = static::get_nav_menu_with_primary_slug( $classic_nav_menus ); + + if ( $nav_menu ) { + return $nav_menu; + } + + return static::get_most_recently_created_nav_menu( $classic_nav_menus ); + } + + + /** + * Sorts the classic menus and returns the most recently created one. + * + * @param WP_Term[] $classic_nav_menus Array of classic nav menu term objects. + * @return WP_Term The most recently created classic nav menu. + */ + private static function get_most_recently_created_nav_menu( $classic_nav_menus ) { + usort( + $classic_nav_menus, + static function( $a, $b ) { + return $b->term_id - $a->term_id; + } + ); + + return $classic_nav_menus[0]; + } + + /** + * Returns the classic menu with the slug `primary` if it exists. + * + * @param WP_Term[] $classic_nav_menus Array of classic nav menu term objects. + * @return WP_Term|null The classic nav menu with the slug `primary` or null. + */ + private static function get_nav_menu_with_primary_slug( $classic_nav_menus ) { + foreach ( $classic_nav_menus as $classic_nav_menu ) { + if ( 'primary' === $classic_nav_menu->slug ) { + return $classic_nav_menu; + } + } + + return null; + } + + + /** + * Gets the classic menu assigned to the `primary` navigation menu location + * if it exists. + * + * @return WP_Term|null The classic nav menu assigned to the `primary` location or null. + */ + private static function get_nav_menu_at_primary_location() { + $locations = get_nav_menu_locations(); + + if ( isset( $locations['primary'] ) ) { + $primary_menu = wp_get_nav_menu_object( $locations['primary'] ); + + if ( $primary_menu ) { + return $primary_menu; + } + } + + return null; + } + + /** + * Creates a default Navigation Block Menu fallback. + * + * @return int|WP_Error The post ID of the default fallback menu or a WP_Error object. + */ + private static function create_default_fallback() { + + $default_blocks = static::get_default_fallback_blocks(); + + // Create a new navigation menu from the fallback blocks. + $default_fallback = wp_insert_post( + array( + 'post_content' => $default_blocks, + 'post_title' => _x( 'Navigation', 'Title of a Navigation menu', 'gutenberg' ), + 'post_name' => 'navigation', + 'post_status' => 'publish', + 'post_type' => 'wp_navigation', + ), + true // So that we can check whether the result is an error. + ); + + return $default_fallback; + } + + /** + * Gets the rendered markup for the default fallback blocks. + * + * @return string default blocks markup to use a the fallback. + */ + private static function get_default_fallback_blocks() { + $registry = WP_Block_Type_Registry::get_instance(); + + // If `core/page-list` is not registered then use empty blocks. + return $registry->is_registered( 'core/page-list' ) ? '' : ''; + } +} diff --git a/lib/experimental/class-wp-rest-navigation-fallback-controller.php b/lib/experimental/class-wp-rest-navigation-fallback-controller.php new file mode 100644 index 00000000000000..ebc1e5b9ff06a7 --- /dev/null +++ b/lib/experimental/class-wp-rest-navigation-fallback-controller.php @@ -0,0 +1,183 @@ +namespace = 'wp-block-editor/v1'; + $this->rest_base = 'navigation-fallback'; + $this->post_type = 'wp_navigation'; + } + + /** + * Registers the controllers routes. + * + * @return void + */ + public function register_routes() { + + // Lists a single nav item based on the given id or slug. + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::READABLE ), + ), + 'schema' => array( $this, 'get_item_schema' ), + ) + ); + } + + /** + * Checks if a given request has access to read fallbacks. + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + */ + public function get_item_permissions_check( $request ) { + + $post_type = get_post_type_object( $this->post_type ); + + // Getting fallbacks requires creating and reading `wp_navigation` posts. + if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( 'edit_theme_options' ) || ! current_user_can( 'edit_posts' ) ) { + return new WP_Error( + 'rest_cannot_create', + __( 'Sorry, you are not allowed to create Navigation Menus as this user.', 'gutenberg' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) { + return new WP_Error( + 'rest_forbidden_context', + __( 'Sorry, you are not allowed to edit Navigation Menus as this user.', 'gutenberg' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + return true; + } + + /** + * Gets the most appropriate fallback Navigation Menu. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_item( $request ) { + $post = WP_Navigation_Fallback_Gutenberg::get_fallback(); + + if ( empty( $post ) ) { + return rest_ensure_response( new WP_Error( 'no_fallback_menu', __( 'No fallback menu found.', 'gutenberg' ), array( 'status' => 404 ) ) ); + } + + $response = $this->prepare_item_for_response( $post, $request ); + + return $response; + } + + /** + * Retrieves the fallbacks' schema, conforming to JSON Schema. + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $this->schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'navigation-fallback', + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => __( 'The unique identifier for the Navigation Menu.', 'gutenberg' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + ), + ); + + return $this->add_additional_fields_schema( $this->schema ); + } + + /** + * Matches the post data to the schema we want. + * + * @param WP_Post $item The wp_navigation Post object whose response is being prepared. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response $response The response data. + */ + public function prepare_item_for_response( $item, $request ) { + $data = array(); + + $fields = $this->get_fields_for_response( $request ); + + if ( rest_is_field_included( 'id', $fields ) ) { + $data['id'] = (int) $item->ID; + } + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + $response = rest_ensure_response( $data ); + + if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { + $links = $this->prepare_links( $item ); + $response->add_links( $links ); + } + + return $response; + } + + /** + * Prepares the links for the request. + * + * @param WP_Post $post the Navigation Menu post object. + * @return array Links for the given request. + */ + private function prepare_links( $post ) { + return array( + 'self' => array( + 'href' => rest_url( rest_get_route_for_post( $post->ID ) ), + 'embeddable' => true, + ), + ); + } +} diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php index 03e7d6f1b97e09..bf9acb7b70d4dd 100644 --- a/lib/experimental/editor-settings.php +++ b/lib/experimental/editor-settings.php @@ -83,6 +83,25 @@ function gutenberg_enable_experiments() { if ( $gutenberg_experiments && array_key_exists( 'gutenberg-color-randomizer', $gutenberg_experiments ) ) { wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableColorRandomizer = true', 'before' ); } + if ( $gutenberg_experiments && array_key_exists( 'gutenberg-command-center', $gutenberg_experiments ) ) { + wp_add_inline_script( 'wp-edit-site', 'window.__experimentalEnableCommandCenter = true', 'before' ); + } + if ( $gutenberg_experiments && array_key_exists( 'gutenberg-command-center', $gutenberg_experiments ) ) { + wp_add_inline_script( 'wp-edit-post', 'window.__experimentalEnableCommandCenter = true', 'before' ); + } + if ( $gutenberg_experiments && array_key_exists( 'gutenberg-group-grid-variation', $gutenberg_experiments ) ) { + wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableGroupGridVariation = true', 'before' ); + } + if ( $gutenberg_experiments && array_key_exists( 'gutenberg-details-blocks', $gutenberg_experiments ) ) { + wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableDetailsBlocks = true', 'before' ); + } + if ( $gutenberg_experiments && array_key_exists( 'gutenberg-theme-previews', $gutenberg_experiments ) ) { + wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableThemePreviews = true', 'before' ); + } + if ( $gutenberg_experiments && array_key_exists( 'gutenberg-pattern-enhancements', $gutenberg_experiments ) ) { + wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnablePatternEnhancements = true', 'before' ); + } + } add_action( 'admin_init', 'gutenberg_enable_experiments' ); diff --git a/lib/experimental/fonts-api/bc-layer/class-gutenberg-fonts-api-bc-layer.php b/lib/experimental/fonts-api/bc-layer/class-gutenberg-fonts-api-bc-layer.php new file mode 100644 index 00000000000000..8c859769875230 --- /dev/null +++ b/lib/experimental/fonts-api/bc-layer/class-gutenberg-fonts-api-bc-layer.php @@ -0,0 +1,96 @@ +wp_fonts = ! empty( $wp_fonts ) ? $wp_fonts : wp_fonts(); + } /** * Gets the font slug. @@ -39,9 +53,22 @@ public static function get_font_slug( $to_convert ) { $message = is_array( $to_convert ) ? 'Use WP_Fonts_Utils::get_font_family_from_variation() to get the font family from an array and then WP_Fonts_Utils::convert_font_family_into_handle() to convert the font-family name into a handle' : 'Use WP_Fonts_Utils::convert_font_family_into_handle() to convert the font-family name into a handle'; - _deprecated_function( __METHOD__, 'X.X.X', $message ); + _deprecated_function( __METHOD__, 'GB 14.9.1', $message ); + + if ( empty( $to_convert ) ) { + return false; + } - return static::_get_font_slug( $to_convert ); + $font_family_name = is_array( $to_convert ) + ? WP_Fonts_Utils::get_font_family_from_variation( $to_convert ) + : $to_convert; + + $slug = false; + if ( ! empty( $font_family_name ) ) { + $slug = WP_Fonts_Utils::convert_font_family_into_handle( $font_family_name ); + } + + return $slug; } /** @@ -54,6 +81,20 @@ public static function init() { _deprecated_function( __METHOD__, 'GB 14.9.1', 'wp_fonts()' ); } + /** + * Get the list of all registered font family handles. + * + * @since X.X.X + * @deprecated GB 15.8.0 Use wp_fonts()->get_registered_font_families(). + * + * @return string[] + */ + public function get_registered_font_families() { + _deprecated_function( __METHOD__, 'GB 15.8.0', 'wp_fonts()->get_registered_font_families()' ); + + return $this->wp_fonts->get_registered_font_families(); + } + /** * Gets the list of registered fonts. * @@ -79,7 +120,7 @@ public function get_registered_webfonts() { public function get_enqueued_webfonts() { _deprecated_function( __METHOD__, 'GB 14.9.1', 'wp_fonts()->get_enqueued()' ); - return $this->queue; + return $this->wp_fonts->queue; } /** @@ -114,21 +155,35 @@ public function register_webfont( array $webfont, $font_family_handle = '', $var _deprecated_function( __METHOD__, 'GB 14.9.1', 'wp_register_fonts()' ); } - // When font family's handle is not passed, attempt to get it from the variation. - if ( ! WP_Fonts_Utils::is_defined( $font_family_handle ) ) { - $font_family = WP_Fonts_Utils::get_font_family_from_variation( $webfont ); - if ( $font_family ) { - $font_family_handle = WP_Fonts_Utils::convert_font_family_into_handle( $font_family ); - } + // Bail out if no variation passed as there's not to register. + if ( empty( $webfont ) ) { + return false; + } + + // Restructure definition: keyed by font-family and array of variations. + $font = array( $webfont ); + if ( WP_Fonts_Utils::is_defined( $font_family_handle ) ) { + $font = array( $font_family_handle => $font ); + } else { + $font = Gutenberg_Fonts_API_BC_Layer::migrate_deprecated_structure( $font, true ); + $font_family_handle = array_key_first( $font ); } - if ( empty( $font_family_handle ) ) { + if ( empty( $font ) || empty( $font_family_handle ) ) { return false; } - return $this->add_variation( $font_family_handle, $webfont, $variation_handle ) - ? $font_family_handle - : false; + // If the variation handle was passed, add it as variation key. + if ( WP_Fonts_Utils::is_defined( $variation_handle ) ) { + $font[ $font_family_handle ] = array( $variation_handle => $font[ $font_family_handle ][0] ); + } + + // Register with the Fonts API. + $handle = wp_register_fonts( $font ); + if ( empty( $handle ) ) { + return false; + } + return array_pop( $handle ); } /** @@ -147,82 +202,6 @@ public function enqueue_webfont( $font_family_name ) { return true; } - /** - * Migrates deprecated webfonts structure into new API data structure, - * i.e. variations grouped by their font-family. - * - * @param array $webfonts Array of webfonts to migrate. - * @return array - */ - public function migrate_deprecated_structure( array $webfonts ) { - $message = 'A deprecated fonts array structure passed to wp_register_fonts(). ' . - 'Variations must be grouped and keyed by their font family.'; - _deprecated_argument( __METHOD__, '14.9.1', $message ); - - $new_webfonts = array(); - foreach ( $webfonts as $webfont ) { - $font_family = WP_Fonts_Utils::get_font_family_from_variation( $webfont ); - if ( ! $font_family ) { - continue; - } - - if ( ! isset( $new_webfonts[ $font_family ] ) ) { - $new_webfonts[ $font_family ] = array(); - } - - $new_webfonts[ $font_family ][] = $webfont; - } - - return $new_webfonts; - } - - /** - * Determines if the given webfonts array is the deprecated array structure. - * - * @param array $webfonts Array of webfonts to check. - * @return bool True when deprecated structure, else false. - */ - public function is_deprecated_structure( array $webfonts ) { - // Checks the first key to determine if it's empty or non-string. - foreach ( $webfonts as $font_family => $variations ) { - return ! WP_Fonts_Utils::is_defined( $font_family ); - } - } - - /** - * Handle the deprecated web fonts structure. - * - * @param array $webfont Web font for extracting font family. - * @param string $message Deprecation message to throw. - * @return string|null The font family slug if successfully registered. Else null. - */ - protected function extract_font_family_from_deprecated_webfonts_structure( array $webfont, $message ) { - _deprecated_argument( __METHOD__, '14.9.1', $message ); - - $font_family = WP_Fonts_Utils::get_font_family_from_variation( $webfont ); - if ( ! $font_family ) { - return null; - } - - return WP_Fonts_Utils::convert_font_family_into_handle( $font_family ); - } - - /** - * Gets the font slug. - * - * Helper function for reuse without the deprecation. - * - * @param array|string $to_convert The value to convert into a slug. Expected as the web font's array - * or a font-family as a string. - * @return string|false The font slug on success, or false if the font-family cannot be determined. - */ - private static function _get_font_slug( $to_convert ) { - $font_family_name = is_array( $to_convert ) ? WP_Fonts_Utils::get_font_family_from_variation( $to_convert ) : $to_convert; - return ! empty( $font_family_name ) - ? WP_Fonts_Utils::convert_font_family_into_handle( $font_family_name ) - : false; - } - /** * Gets the registered webfonts in the original web font property structure keyed by each handle. * @@ -233,7 +212,7 @@ private function _get_registered_webfonts() { $registered = array(); // Find the registered font families. - foreach ( $this->registered as $handle => $obj ) { + foreach ( $this->wp_fonts->registered as $handle => $obj ) { if ( ! $obj->extra['is_font_family'] ) { continue; } @@ -248,7 +227,7 @@ private function _get_registered_webfonts() { // Build the return array structure. foreach ( $font_families as $font_family_handle => $variations ) { foreach ( $variations as $variation_handle ) { - $variation_obj = $this->registered[ $variation_handle ]; + $variation_obj = $this->wp_fonts->registered[ $variation_handle ]; $registered[ $font_family_handle ][ $variation_handle ] = $variation_obj->extra['font-properties']; } @@ -256,40 +235,4 @@ private function _get_registered_webfonts() { return $registered; } - - /** - * Gets the enqueued webfonts in the original web font property structure keyed by each handle. - * - * @return array[] - */ - private function _get_enqueued_webfonts() { - $enqueued = array(); - foreach ( $this->queue as $handle ) { - // Skip if not registered. - if ( ! isset( $this->registered[ $handle ] ) ) { - continue; - } - - // Skip if already found. - if ( isset( $enqueued[ $handle ] ) ) { - continue; - } - - $obj = $this->registered[ $handle ]; - - // If a variation, add it. - if ( ! $obj->extra['is_font_family'] ) { - $enqueued[ $handle ] = $obj->extra['font-properties']; - continue; - } - - // If font-family, add all of its variations. - foreach ( $obj->deps as $variation_handle ) { - $obj = $this->registered[ $variation_handle ]; - $enqueued[ $variation_handle ] = $obj->extra['font-properties']; - } - } - - return $enqueued; - } } diff --git a/lib/experimental/fonts-api/deprecations/webfonts-deprecations.php b/lib/experimental/fonts-api/bc-layer/webfonts-deprecations.php similarity index 95% rename from lib/experimental/fonts-api/deprecations/webfonts-deprecations.php rename to lib/experimental/fonts-api/bc-layer/webfonts-deprecations.php index 7179370471ad1e..7a3a7bd013eea3 100644 --- a/lib/experimental/fonts-api/deprecations/webfonts-deprecations.php +++ b/lib/experimental/fonts-api/bc-layer/webfonts-deprecations.php @@ -17,16 +17,18 @@ * @since X.X.X * @deprecated GB 15.1 Use wp_fonts() instead. * - * @global WP_Web_Fonts $wp_webfonts + * @global WP_Webfonts $wp_webfonts * - * @return WP_Web_Fonts WP_Web_Fonts instance. + * @return WP_Webfonts WP_Webfonts instance. */ function wp_webfonts() { _deprecated_function( __FUNCTION__, 'GB 15.1', 'wp_fonts()' ); global $wp_webfonts; - $wp_webfonts = wp_fonts(); + if ( ! ( $wp_webfonts instanceof WP_Webfonts ) ) { + $wp_webfonts = new WP_Webfonts( wp_fonts() ); + } return $wp_webfonts; } @@ -59,6 +61,8 @@ function wp_webfonts() { function wp_register_webfonts( array $webfonts ) { _deprecated_function( __FUNCTION__, 'GB 15.1', 'wp_register_fonts()' ); + $webfonts = Gutenberg_Fonts_API_BC_Layer::migrate_deprecated_structure( $webfonts ); + return wp_register_fonts( $webfonts ); } } @@ -249,6 +253,8 @@ function wp_register_webfont_provider( $name, $classname ) { * An empty array if none were processed. */ function wp_print_webfonts( $handles = false ) { + _deprecated_function( __FUNCTION__, 'GB 15.1', 'wp_print_fonts' ); + return wp_print_fonts( $handles ); } } diff --git a/lib/experimental/fonts-api/class-wp-fonts-provider-local.php b/lib/experimental/fonts-api/class-wp-fonts-provider-local.php index cea74c16a95f47..a5dbdb707e41ff 100644 --- a/lib/experimental/fonts-api/class-wp-fonts-provider-local.php +++ b/lib/experimental/fonts-api/class-wp-fonts-provider-local.php @@ -179,7 +179,7 @@ private function build_font_face_css( array $font ) { // Compile the "src" parameter. if ( 'src' === $key ) { - $value = $this->compile_src( $font['font-family'], $value ); + $value = $this->compile_src( $value ); } // If font-variation-settings is an array, convert it to a string. @@ -200,11 +200,10 @@ private function build_font_face_css( array $font ) { * * @since X.X.X * - * @param string $font_family Font family. - * @param array $value Value to process. + * @param array $value Value to process. * @return string The CSS. */ - private function compile_src( $font_family, array $value ) { + private function compile_src( array $value ) { $src = ''; foreach ( $value as $item ) { diff --git a/lib/experimental/fonts-api/class-wp-fonts.php b/lib/experimental/fonts-api/class-wp-fonts.php index 6a06de8f4c86a5..cbabd3d85d36d7 100644 --- a/lib/experimental/fonts-api/class-wp-fonts.php +++ b/lib/experimental/fonts-api/class-wp-fonts.php @@ -16,7 +16,7 @@ * * @since X.X.X */ -class WP_Fonts extends WP_Webfonts { +class WP_Fonts extends WP_Dependencies { /** * An array of registered providers. diff --git a/lib/experimental/fonts-api/fonts-api.php b/lib/experimental/fonts-api/fonts-api.php index 49b45392d3306a..5f3f7a60ee8839 100644 --- a/lib/experimental/fonts-api/fonts-api.php +++ b/lib/experimental/fonts-api/fonts-api.php @@ -22,7 +22,18 @@ function wp_fonts() { if ( ! ( $wp_fonts instanceof WP_Fonts ) ) { $wp_fonts = new 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 ); + } } return $wp_fonts; @@ -56,12 +67,6 @@ function wp_register_fonts( array $fonts ) { $registered = array(); $wp_fonts = wp_fonts(); - // BACKPORT NOTE: Do not backport this code block to Core. - if ( $wp_fonts->is_deprecated_structure( $fonts ) ) { - $fonts = $wp_fonts->migrate_deprecated_structure( $fonts ); - } - // BACKPORT NOTE: end of code block. - foreach ( $fonts as $font_family => $variations ) { $font_family_handle = $wp_fonts->add_font_family( $font_family ); if ( ! $font_family_handle ) { @@ -176,34 +181,52 @@ function wp_register_font_provider( $name, $classname ) { * * @since X.X.X * - * @param string|string[]|false $handles Optional. Items to be processed: queue (false), - * single item (string), or multiple items (array of strings). - * Default false. + * @param string|string[]|bool $handles Optional. Items to be processed: queue (false), + * for iframed editor assets (true), single item (string), + * or multiple items (array of strings). + * Default false. * @return array|string[] Array of font handles that have been processed. * An empty array if none were processed. */ function wp_print_fonts( $handles = false ) { - global $wp_fonts; + $wp_fonts = wp_fonts(); + $registered = $wp_fonts->get_registered_font_families(); + $in_iframed_editor = true === $handles; + + // Nothing to print, as no fonts are registered. + if ( empty( $registered ) ) { + return array(); + } - if ( empty( $handles ) ) { - $handles = false; + // Skip this reassignment decision-making when using the default of `false`. + if ( false !== $handles ) { + // When `true`, print all registered fonts for the iframed editor. + if ( $in_iframed_editor ) { + $queue = $wp_fonts->queue; + $done = $wp_fonts->done; + $wp_fonts->done = array(); + $wp_fonts->queue = $registered; + $handles = false; + } elseif ( empty( $handles ) ) { + // When falsey, assign `false` to print enqueued fonts. + $handles = false; + } } _wp_scripts_maybe_doing_it_wrong( __FUNCTION__ ); - if ( ! ( $wp_fonts instanceof WP_Fonts ) ) { - if ( ! $handles ) { - return array(); // No need to instantiate if nothing is there. - } + $printed = $wp_fonts->do_items( $handles ); + + // Reset the API. + if ( $in_iframed_editor ) { + $wp_fonts->done = $done; + $wp_fonts->queue = $queue; } - return wp_fonts()->do_items( $handles ); + return $printed; } } -add_action( 'admin_print_styles', 'wp_print_fonts', 50 ); -add_action( 'wp_head', 'wp_print_fonts', 50 ); - /** * Add webfonts mime types. */ diff --git a/lib/experimental/interactivity-api/blocks.php b/lib/experimental/interactivity-api/blocks.php new file mode 100644 index 00000000000000..755c1d1d4fa7d7 --- /dev/null +++ b/lib/experimental/interactivity-api/blocks.php @@ -0,0 +1,263 @@ +attributes['displayPreview'] ) ) { + return $block_content; + } + $processor = new WP_HTML_Tag_Processor( $block_content ); + $processor->next_tag(); + $processor->set_attribute( 'data-wp-island', '' ); + $processor->next_tag( 'object' ); + $processor->set_attribute( 'data-wp-bind.hidden', 'selectors.core.file.hasNoPdfPreview' ); + $processor->set_attribute( 'hidden', true ); + return $processor->get_updated_html(); +} +add_filter( 'render_block_core/file', 'gutenberg_block_core_file_add_directives_to_content', 10, 3 ); + +/** + * Add Interactivity API directives to the navigation block markup using the Tag Processor + * The final HTML of the navigation block will look similar to this: + * + *
+ *
+ *
+ * + * + * @param string $block_content Markup of the navigation block. + * + * @return string Navigation block markup with the proper directives + */ +function gutenberg_block_core_navigation_add_directives_to_markup( $block_content ) { + $w = new WP_HTML_Tag_Processor( $block_content ); + // Add directives to the `